better support for sqlalchemy storage
authorEmmanuel Garette <egarette@cadoles.com>
Sun, 6 Jul 2014 13:31:57 +0000 (15:31 +0200)
committerEmmanuel Garette <egarette@cadoles.com>
Sun, 6 Jul 2014 13:31:57 +0000 (15:31 +0200)
16 files changed:
test/test_dyn_optiondescription.py
test/test_option_setting.py
tiramisu/autolib.py
tiramisu/option/baseoption.py
tiramisu/option/masterslave.py
tiramisu/option/option.py
tiramisu/option/optiondescription.py
tiramisu/setting.py
tiramisu/storage/__init__.py
tiramisu/storage/dictionary/__init__.py
tiramisu/storage/dictionary/option.py
tiramisu/storage/dictionary/setting.py
tiramisu/storage/dictionary/value.py
tiramisu/storage/sqlalchemy/__init__.py
tiramisu/storage/sqlalchemy/option.py
tiramisu/value.py

index 446d160..cdb6ce1 100644 (file)
@@ -37,6 +37,14 @@ def return_list(val=None):
         return ['val1', 'val2']
 
 
+def return_same_list():
+    return ['val1', 'val1']
+
+
+def return_wrong_list():
+    return ['---', ' ']
+
+
 def test_build_dyndescription():
     st = StrOption('st', '')
     dod = DynOptionDescription('dod', '', [st], callback=return_list)
@@ -193,18 +201,18 @@ def test_prop_dyndescription():
     stval2 = cfg.unwrap_from_path('od.dodval2.stval2')
     dodval1 = cfg.unwrap_from_path('od.dodval1')
     dodval2 = cfg.unwrap_from_path('od.dodval2')
-    assert str(cfg.cfgimpl_get_settings()[stval1]) == str(['test'])
-    assert str(cfg.cfgimpl_get_settings()[stval2]) == str(['test'])
+    assert str(cfg.cfgimpl_get_settings()[stval1]) in [str(['test']), str([u'test'])]
+    assert str(cfg.cfgimpl_get_settings()[stval2]) in [str(['test']), str([u'test'])]
     cfg.cfgimpl_get_settings()[stval2].append('test2')
-    assert str(cfg.cfgimpl_get_settings()[stval1]) == str(['test'])
-    assert str(cfg.cfgimpl_get_settings()[stval2]) == str(['test', 'test2'])
+    assert str(cfg.cfgimpl_get_settings()[stval1]) in [str(['test']), str([u'test'])]
+    assert str(cfg.cfgimpl_get_settings()[stval2]) in [str(['test', 'test2']), str([u'test', 'test2'])]
     cfg.cfgimpl_get_settings()[stval1].remove('test')
     assert str(cfg.cfgimpl_get_settings()[stval1]) == str([])
     #
     assert str(cfg.cfgimpl_get_settings()[dodval1]) == str([])
     assert str(cfg.cfgimpl_get_settings()[dodval2]) == str([])
     cfg.cfgimpl_get_settings()[dodval1].append('test1')
-    assert str(cfg.cfgimpl_get_settings()[dodval1]) == str(['test1'])
+    assert str(cfg.cfgimpl_get_settings()[dodval1]) in [str(['test1']), str([u'test1'])]
     assert str(cfg.cfgimpl_get_settings()[dodval2]) == str([])
     cfg.cfgimpl_get_settings()[dodval1].remove('test1')
     assert str(cfg.cfgimpl_get_settings()[dodval1]) == str([])
@@ -402,11 +410,11 @@ def test_prop_dyndescription_context():
     cfg = Config(od2)
     stval1 = cfg.unwrap_from_path('od.dodval1.stval1')
     stval2 = cfg.unwrap_from_path('od.dodval2.stval2')
-    assert str(cfg.cfgimpl_get_settings()[stval1]) == str(['test'])
-    assert str(cfg.cfgimpl_get_settings()[stval2]) == str(['test'])
+    assert str(cfg.cfgimpl_get_settings()[stval1]) in [str(['test']), str([u'test'])]
+    assert str(cfg.cfgimpl_get_settings()[stval2]) in [str(['test']), str([u'test'])]
     cfg.cfgimpl_get_settings()[stval2].append('test2')
-    assert str(cfg.cfgimpl_get_settings()[stval1]) == str(['test'])
-    assert str(cfg.cfgimpl_get_settings()[stval2]) == str(['test', 'test2'])
+    assert str(cfg.cfgimpl_get_settings()[stval1]) in [str(['test']), str([u'test'])]
+    assert str(cfg.cfgimpl_get_settings()[stval2]) in [str(['test', 'test2']), str([u'test', 'test2'])]
     cfg.cfgimpl_get_settings()[stval1].remove('test')
     assert str(cfg.cfgimpl_get_settings()[stval1]) == str([])
 
@@ -1293,13 +1301,11 @@ def test_invalid_symlink_dyndescription():
 
 def test_nocallback_dyndescription():
     st = StrOption('st', '')
-    st2 = StrOption('st2', st)
+    st2 = StrOption('st2', '')
     raises(ConfigError, "DynOptionDescription('dod', '', [st, st2])")
 
 
 def test_invalid_samevalue_dyndescription():
-    def return_same_list():
-        return ['val1', 'val1']
     st = StrOption('st', '')
     dod = DynOptionDescription('dod', '', [st], callback=return_same_list)
     od = OptionDescription('od', '', [dod])
@@ -1308,10 +1314,8 @@ def test_invalid_samevalue_dyndescription():
 
 
 def test_invalid_name_dyndescription():
-    def return_same_list():
-        return ['---', ' ']
     st = StrOption('st', '')
-    dod = DynOptionDescription('dod', '', [st], callback=return_same_list)
+    dod = DynOptionDescription('dod', '', [st], callback=return_wrong_list)
     od = OptionDescription('od', '', [dod])
     cfg = Config(od)
     raises(ValueError, "print cfg")
index cd38a48..193955e 100644 (file)
@@ -382,4 +382,4 @@ def test_properties_cached():
     setting = c.cfgimpl_get_settings()
     option = c.cfgimpl_get_description().sub.b1
     c._setattr('sub.b1', True, force_permissive=True)
-    assert str(setting[b1]) == "['test']"
+    assert str(setting[b1]) in ["['test']", "[u'test']"]
index 41ad226..44bb82e 100644 (file)
@@ -149,7 +149,8 @@ def carry_out_calculation(option, config, callback, callback_params,
             if isinstance(callbk, tuple):
                 if config is undefined:
                     raise ContextError()  # pragma: optional cover
-                if len(callbk) == 1:  # pragma: optional cover
+                if callbk[0] is None:  # pragma: optional cover
+                    #Not an option, set full context
                     tcparams.setdefault(key, []).append((config, False))
                 else:
                     # callbk is something link (opt, True|False)
index 06f4bb4..8eec7fa 100644 (file)
@@ -33,11 +33,7 @@ from tiramisu.storage import get_storages_option
 StorageBase = get_storages_option('base')
 
 
-class SubMulti(object):
-    pass
-
-
-submulti = SubMulti()
+submulti = 2
 
 
 allowed_character = '[a-z\d\-_]'
@@ -115,7 +111,10 @@ class Base(StorageBase):
         if callback is not None:
             validate_callback(callback, callback_params, 'callback')
             self._callback = callback
-            self._callback_params = callback_params
+            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,
@@ -143,8 +142,13 @@ class Base(StorageBase):
                 raise ValueError(_("invalid default_multi value {0} "
                                    "for option {1}: {2}").format(
                                        str(default_multi), name, err))
-        self._multi = multi
-        if self._multi is not False:
+        if multi is True:
+            self._multi = 0
+        elif multi is False:
+            self._multi = 1
+        elif multi is submulti:
+            self._multi = submulti
+        if self._multi != 1:
             if default is None:
                 default = []
             self._default_multi = default_multi
@@ -260,10 +264,10 @@ class BaseOption(Base):
             return
         if not load and self._callback is None:
             self._state_callback = None
-            self._state_callback_params = None
+            self._state_callback_params = {}
         elif load and self._state_callback is None:
             self._callback = None
-            self._callback_params = None
+            self._callback_params = {}
             del(self._state_callback)
             del(self._state_callback_params)
         else:
@@ -273,7 +277,7 @@ class BaseOption(Base):
             else:
                 callback = self._callback
                 callback_params = self._callback_params
-                self._state_callback_params = None
+                self._state_callback_params = {}
             if callback_params is not None:
                 cllbck_prms = {}
                 for key, values in callback_params.items():
@@ -429,6 +433,19 @@ class BaseOption(Base):
     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
+
+    def _is_subdyn(self):
+        try:
+            return self._subdyn is not None
+        except AttributeError:
+            return False
+
+    def impl_getproperties(self):
+        return self._properties
+
 
 class OnlyOption(BaseOption):
     __slots__ = tuple()
@@ -713,18 +730,11 @@ class Option(OnlyOption):
         "accesses the Option's doc"
         return self.impl_get_information('doc')
 
-    def impl_has_callback(self):
-        "to know if a callback has been defined or not"
-        if self._callback is None:
-            return False
-        else:
-            return True
-
     #def impl_getkey(self, value):
     #    return value
 
     def impl_is_multi(self):
-        return self._multi is True or self._multi is submulti
+        return self._multi == 0 or self._multi is submulti
 
     def impl_is_submulti(self):
         return self._multi is submulti
@@ -746,7 +756,7 @@ class Option(OnlyOption):
                                        self._name))
         warnings_only = params.get('warnings_only', False)
         if self._is_subdyn():
-            dynod = self._subdyn
+            dynod = self._impl_getsubdyn()
         else:
             dynod = None
         for opt in other_opts:
@@ -756,10 +766,10 @@ class Option(OnlyOption):
                 if dynod is None:
                     raise ConfigError(_('almost one option in consistency is '
                                         'in a dynoptiondescription but not all'))
-                if dynod != opt._subdyn:
+                if dynod != opt._impl_getsubdyn():
                     raise ConfigError(_('option in consistency must be in same'
                                         ' dynoptiondescription'))
-                dynod = opt._subdyn
+                dynod = opt._impl_getsubdyn()
             elif dynod is not None:
                 raise ConfigError(_('almost one option in consistency is in a '
                                     'dynoptiondescription but not all'))
@@ -847,10 +857,10 @@ class Option(OnlyOption):
             default_multi = self._default_multi
         except AttributeError:
             default_multi = None
-        if callback is not None and ((not self._multi and
+        if callback is not None and ((self._multi == 1 and
                                       (self._default is not None or
                                        default_multi is not None))
-                                     or (self._multi and
+                                     or (self._multi != 1 and
                                          (self._default != [] or
                                           default_multi is not None))
                                      ):  # pragma: optional cover
@@ -983,6 +993,23 @@ class SymLinkOption(OnlyOption):
     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 impl_getproperties(self):
+        return self._opt._properties
+
+    def impl_get_callback(self):
+        return self._opt._callback, self._opt._callback_params
+
+    def impl_has_callback(self):
+        "to know if a callback has been defined or not"
+        return self._opt._callback is not None
+
+    def _is_subdyn(self):
+        try:
+            return self._opt._subdyn is not None
+        except AttributeError:
+            return False
+
 
 class DynSymLinkOption(SymLinkOption):
     __slots__ = ('_dyn',)
index f7dadf9..ee72ec1 100644 (file)
@@ -55,7 +55,7 @@ class MasterSlaves(object):
                                ).format(name))
         if validate:
             callback, callback_params = self.master.impl_get_callback()
-            if callback is not None and callback_params is not None:  # pragma: optional cover
+            if callback is not None and callback_params != {}:  # pragma: optional cover
                 for key, callbacks in callback_params.items():
                     for callbk in callbacks:
                         if isinstance(callbk, tuple):
index 48541b4..1f5df9d 100644 (file)
@@ -48,11 +48,14 @@ class ChoiceOption(Option):
         """
         if isinstance(values, FunctionType):
             validate_callback(values, values_params, 'values')
-        elif not isinstance(values, tuple):  # pragma: optional cover
-            raise TypeError(_('values must be a tuple or a function for {0}'
-                              ).format(name))
-        self._extra = {'_choice_values': values,
-                       '_choice_values_params': values_params}
+        else:
+            if values_params is not None:
+                raise ValueError(_('values is not a function, so values_params must be None'))
+            if not isinstance(values, tuple):  # pragma: optional cover
+                raise TypeError(_('values must be a tuple or a function for {0}'
+                                  ).format(name))
+        self._choice_values = values
+        self._choice_values_params = values_params
         super(ChoiceOption, self).__init__(name, doc, default=default,
                                            default_multi=default_multi,
                                            callback=callback,
@@ -66,9 +69,9 @@ class ChoiceOption(Option):
 
     def impl_get_values(self, context):
         #FIXME cache? but in context...
-        values = self._extra['_choice_values']
+        values = self._choice_values
         if isinstance(values, FunctionType):
-            values_params = self._extra['_choice_values_params']
+            values_params = self._choice_values_params
             if values_params is None:
                 values_params = {}
             values = carry_out_calculation(self, config=context,
index f8f3f75..f9e9401 100644 (file)
@@ -74,7 +74,6 @@ class OptionDescription(BaseOption, StorageOptionDescription):
                                               'dynoptiondescription'))
             old = child
         self._add_children(child_names, 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
@@ -167,9 +166,6 @@ class OptionDescription(BaseOption, StorageOptionDescription):
             raise ValueError(_('group_type: {0}'
                                ' not allowed').format(group_type))
 
-    def impl_get_group_type(self):
-        return self._group_type
-
     def _valid_consistency(self, option, value, context, index, submulti_idx):
         if self._cache_consistencies is None:
             return True
@@ -239,7 +235,6 @@ class OptionDescription(BaseOption, StorageOptionDescription):
         :param descr: parent :class:`tiramisu.option.OptionDescription`
         """
         if descr is None:
-            self._cache_paths = None
             self._cache_consistencies = None
             self.impl_build_cache_option()
             descr = self
@@ -261,8 +256,6 @@ class OptionDescription(BaseOption, StorageOptionDescription):
 
     def _impl_get_suffixes(self, context):
         callback, callback_params = self.impl_get_callback()
-        if callback_params is None:
-            callback_params = {}
         values = carry_out_calculation(self, config=context,
                                        callback=callback,
                                        callback_params=callback_params)
@@ -275,10 +268,9 @@ class OptionDescription(BaseOption, StorageOptionDescription):
 
     def _impl_search_dynchild(self, name=undefined, context=undefined):
         ret = []
-        for child in self._impl_st_getchildren():
+        for child in self._impl_st_getchildren(context, only_dyn=True):
             cname = child.impl_getname()
-            if isinstance(child, DynOptionDescription) and \
-                    (name is undefined or name.startswith(cname)):
+            if name is undefined or name.startswith(cname):
                 path = cname
                 for value in child._impl_get_suffixes(context):
                     if name is undefined:
@@ -296,7 +288,7 @@ class OptionDescription(BaseOption, StorageOptionDescription):
             return child._impl_to_dyn(name, path)
 
     def _impl_getchildren(self, dyn=True, context=undefined):
-        for child in self._impl_st_getchildren():
+        for child in self._impl_st_getchildren(context):
             cname = child._name
             if dyn and child.impl_is_dynoptiondescription():
                 path = cname
@@ -310,24 +302,30 @@ class OptionDescription(BaseOption, StorageOptionDescription):
     def impl_getchildren(self):
         return list(self._impl_getchildren())
 
+    def __getattr__(self, name, context=undefined):
+        if name.startswith('_'):  # or name.startswith('impl_'):
+            return object.__getattribute__(self, name)
+        return self._getattr(name, context=context)
+
 
 class DynOptionDescription(OptionDescription):
     def __init__(self, name, doc, children, requires=None, properties=None,
                  callback=None, callback_params=None):
+        super(DynOptionDescription, self).__init__(name, doc, children,
+                                                   requires, properties)
         for child in children:
             if isinstance(child, OptionDescription):
                 if child.impl_get_group_type() != groups.master:
                     raise ConfigError(_('cannot set optiondescription in an '
                                         'dynoptiondescription'))
                 for chld in child._impl_getchildren():
-                    chld._subdyn = self
+                    chld._impl_setsubdyn(self)
             if isinstance(child, SymLinkOption):
                 raise ConfigError(_('cannot set symlinkoption in an '
                                     'dynoptiondescription'))
-            child._subdyn = self
-        super(DynOptionDescription, self).__init__(name, doc, children,
-                                                   requires, properties)
+            child._impl_setsubdyn(self)
         self.impl_set_callback(callback, callback_params)
+        self.commit()
 
     def _validate_callback(self, callback, callback_params):
         if callback is None:
@@ -346,7 +344,7 @@ class SynDynOptionDescription(object):
     def __getattr__(self, name, context=undefined):
         if name in dir(self._opt):
             return getattr(self._opt, name)
-        return self._opt._getattr(name, self._name, self._suffix, context)
+        return self._opt._getattr(name, suffix=self._suffix, context=context)
 
     def impl_getname(self):
         return self._name
index e5871ad..d2435f2 100644 (file)
@@ -100,6 +100,9 @@ rw_append = set(['frozen', 'disabled', 'validator', 'hidden'])
 rw_remove = set(['permissive', 'everything_frozen', 'mandatory'])
 
 
+forbidden_set_properties = set(['force_store_value'])
+
+
 log = getLogger('tiramisu')
 #FIXME
 #import logging
@@ -253,7 +256,7 @@ class Property(object):
                                'this property is calculated').format(
                                    propname, self._opt.impl_getname()))
         self._properties.add(propname)
-        self._setting._setproperties(self._properties, self._opt, self._path)
+        self._setting._setproperties(self._properties, self._path)
 
     def remove(self, propname):
         """Removes a property named propname
@@ -263,8 +266,7 @@ class Property(object):
         """
         if propname in self._properties:
             self._properties.remove(propname)
-            self._setting._setproperties(self._properties, self._opt,
-                                         self._path)
+            self._setting._setproperties(self._properties, self._path)
 
     def extend(self, propnames):
         """Extends properties to the existing properties
@@ -347,7 +349,7 @@ class Settings(object):
         else:
             if opt is not None and _path is None:
                 _path = opt.impl_getpath(self._getcontext())
-            self._p_.reset_properties(_path)
+            self._p_.delproperties(_path)
         self._getcontext().cfgimpl_reset_cache()
 
     def _getproperties(self, opt=None, path=None, _is_apply_req=True):
@@ -367,7 +369,7 @@ class Settings(object):
                 is_cached, props = self._p_.getcache(path, ntime)
                 if is_cached:
                     return copy(props)
-            props = self._p_.getproperties(path, opt._properties)
+            props = self._p_.getproperties(path, opt.impl_getproperties())
             if _is_apply_req:
                 props = copy(props)
                 props |= self.apply_requires(opt, path)
@@ -383,33 +385,28 @@ class Settings(object):
         "puts property propname in the Config's properties attribute"
         props = self._p_.getproperties(None, default_properties)
         props.add(propname)
-        self._setproperties(props, None, None)
+        self._setproperties(props, None)
 
     def remove(self, propname):
         "deletes property propname in the Config's properties attribute"
         props = self._p_.getproperties(None, default_properties)
         if propname in props:
             props.remove(propname)
-            self._setproperties(props, None, None)
+            self._setproperties(props, None)
 
     def extend(self, propnames):
         for propname in propnames:
             self.append(propname)
 
-    def _setproperties(self, properties, opt, path):
-        """save properties for specified opt
+    def _setproperties(self, properties, path):
+        """save properties for specified path
         (never save properties if same has option properties)
         """
-        if opt is None:
-            self._p_.setproperties(None, properties)
-        else:
-            #if opt._calc_properties is not None:
-            #    properties -= opt._calc_properties
-            #if set(opt._properties) == properties:
-            #    self._p_.reset_properties(path)
-            #else:
-            #    self._p_.setproperties(path, properties)
-            self._p_.setproperties(path, properties)
+        forbidden_properties = forbidden_set_properties & properties
+        if forbidden_properties:
+            raise ConfigError(_('cannot add those properties: {0}').format(
+                ' '.join(forbidden_properties)))
+        self._p_.setproperties(path, properties)
         self._getcontext().cfgimpl_reset_cache()
 
     #____________________________________________________________
@@ -622,15 +619,6 @@ class Settings(object):
     def get_modified_permissives(self):
         return self._p_.get_modified_permissives()
 
-    def get_with_property(self, propname):
-        opts, paths = self._getcontext().cfgimpl_get_description(
-        )._cache_paths
-        for index in range(0, len(paths)):
-            opt = opts[index]
-            path = paths[index]
-            if propname in self._getproperties(opt, path, False):
-                yield (opt, path)
-
     def __getstate__(self):
         return {'_p_': self._p_, '_owner': str(self._owner)}
 
index 5bb0ec8..5e51f1d 100644 (file)
@@ -117,15 +117,20 @@ def get_storages(context, session_id, persistent):
         session_id = gen_id(context)
     imp = storage_type.get()
     storage = imp.Storage(session_id, persistent)
-    return imp.Settings(storage), imp.Values(storage)
+    try:
+        return imp.Settings(storage), imp.Values(storage)
+    except:
+        import traceback
+        traceback.print_exc()
+        raise Exception('rah')
 
 
 def get_storages_option(type_):
     imp = storage_option_type.get()
     if type_ == 'base':
-        return imp.Base
+        return imp.StorageBase
     else:
-        return imp.OptionDescription
+        return imp.StorageOptionDescription
 
 
 def list_sessions(type_):  # pragma: optional cover
index e6f358d..a52bf26 100644 (file)
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors)
+# Copyright (C) 2013-2014 Team tiramisu (see AUTHORS for all contributors)
 #
 # This program is free software: you can redistribute it and/or modify it
 # under the terms of the GNU Lesser General Public License as published by the
@@ -25,7 +25,7 @@ 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 .option import Base, OptionDescription
+from .option import StorageBase, StorageOptionDescription
 
 __all__ = (setting, Values, Settings, Storage, list_sessions, delete_session,
-           Base, OptionDescription)
+           StorageBase, StorageOptionDescription)
index 38a739d..3cb61e7 100644 (file)
 #
 # ____________________________________________________________
 from tiramisu.i18n import _
-from tiramisu.setting import groups, undefined
+from tiramisu.setting import undefined
 from tiramisu.error import ConfigError
 
 
 #____________________________________________________________
 #
 # Base
-class Base(object):
+class StorageBase(object):
     __slots__ = ('_name', '_requires', '_properties', '_readonly',
                  '_calc_properties', '_informations',
                  '_state_readonly', '_state_requires', '_stated',
@@ -34,13 +34,14 @@ class Base(object):
                  '_state_callback_params', '_callback_params', '_multitype',
                  '_consistencies', '_warnings_only', '_master_slaves',
                  '_state_consistencies', '_extra', '_subdyn', '__weakref__',
-                 '_state_master_slaves')
+                 '_state_master_slaves', '_choice_values',
+                 '_choice_values_params')
 
     def __init__(self):
         try:
             self._subdyn
         except AttributeError:
-            self._subdyn = False
+            self._subdyn = None
         try:
             self._consistencies
         except AttributeError:
@@ -52,7 +53,7 @@ class Base(object):
         try:
             self._callback_params
         except AttributeError:
-            self._callback_params = None
+            self._callback_params = {}
         try:
             self._validator
         except AttributeError:
@@ -71,19 +72,22 @@ class Base(object):
     def _get_id(self):
         return id(self)
 
-    def _is_subdyn(self):
-        try:
-            return self._subdyn is not False
-        except AttributeError:
-            return False
+    def _impl_getsubdyn(self):
+        return self._subdyn
 
+    def _impl_setsubdyn(self, subdyn):
+        self._subdyn = subdyn
 
-class OptionDescription(Base):
+    def commit(self):
+        pass
+
+
+class StorageOptionDescription(StorageBase):
     __slots__ = ('_children', '_cache_paths', '_cache_consistencies',
                  '_group_type', '_is_build_cache', '_state_group_type')
 
     def __init__(self):
-        pass
+        self._cache_paths = None
 
     def _add_children(self, child_names, children):
         self._children = (tuple(child_names), tuple(children))
@@ -104,10 +108,14 @@ class OptionDescription(Base):
             raise AttributeError(_('no option {0} found').format(opt))
 
     def impl_get_group_type(self):  # pragma: optional cover
-        return getattr(groups, self._group_type)
+        return self._group_type
 
     def impl_build_cache_option(self, _currpath=None, cache_path=None,
                                 cache_option=None):
+        try:
+            self._cache_paths
+        except AttributeError:
+            self._cache_paths = None
         if _currpath is None and self._cache_paths is not None:  # pragma: optional cover
             # cache already set
             return
@@ -145,8 +153,7 @@ class OptionDescription(Base):
                     found = True
                     break
             if not found:
-                #FIXME
-                raise ConfigError(_('hu?'))
+                raise ConfigError(_('cannot find dynpath'))
             subpath = subpath + suffix
             for slength in xrange(length, len(spath)):
                 subpath = subpath + '.' + spath[slength] + suffix
@@ -226,21 +233,17 @@ class OptionDescription(Base):
                 return find_results[0]
         return find_results
 
-    def _impl_st_getchildren(self):
-        return self._children[1]
+    def _impl_st_getchildren(self, context, only_dyn=False):
+        for child in self._children[1]:
+            if only_dyn is False or child.impl_is_dynoptiondescription():
+                yield(child)
 
-    def __getattr__(self, name, context=undefined):
-        if name == '_name':
-            return object.__getattribute__(self, name)
-        return self._getattr(name, context=context)
-
-    def _getattr(self, name, dyn_od=undefined, suffix=undefined,
-                 context=undefined, dyn=True):
+    def _getattr(self, name, suffix=undefined, context=undefined, dyn=True):
         error = False
         if suffix is not undefined:
             try:
-                if undefined in [dyn_od, suffix, context]:  # pragma: optional cover
-                    raise ConfigError(_("dyn_od, suffix and context needed if "
+                if undefined in [suffix, context]:  # pragma: optional cover
+                    raise ConfigError(_("suffix and context needed if "
                                         "it's a dyn option"))
                 if name.endswith(suffix):
                     oname = name[:-len(suffix)]
@@ -270,3 +273,13 @@ class OptionDescription(Base):
             raise AttributeError(_('unknown Option {0} '
                                    'in OptionDescription {1}'
                                    '').format(name, self._name))
+
+    def _get_force_store_value(self):
+        #FIXME faire des tests (notamment pas ajouter à un config)
+        #FIXME devrait faire un cache !
+        opts, paths = self._cache_paths
+        for index in range(0, len(paths)):
+            opt = opts[index]
+            path = paths[index]
+            if 'force_store_value' in opt._properties:
+                yield (opt, path)
index a25c989..fb76109 100644 (file)
@@ -42,13 +42,12 @@ class Settings(Cache):
     def reset_all_properties(self):
         self._properties.clear()
 
-    def reset_properties(self, path):
+    def delproperties(self, path):
         try:
             del(self._properties[path])
         except KeyError:
             pass
 
-    # permissive
     def setpermissive(self, path, permissive):
         self._permissives[path] = frozenset(permissive)
 
index 2876d72..f0ffe3a 100644 (file)
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 "default plugin for value: set it in a simple dictionary"
-# Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors)
+# Copyright (C) 2013-2014 Team tiramisu (see AUTHORS for all contributors)
 #
 # This program is free software: you can redistribute it and/or modify it
 # under the terms of the GNU Lesser General Public License as published by the
index e69de29..827cd2f 100644 (file)
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2014 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# ____________________________________________________________
+"""Default plugin for storage. All informations are store in a simple
+dictionary in memory.
+
+You cannot have persistente informations with this kind of storage.
+
+The advantage of this solution is that you can easily create a Config and
+use it. But if something goes wrong, you will lost your modifications.
+"""
+from .value import Values
+from .setting import Settings
+from .storage import Storage, list_sessions, delete_session, setting
+from .option import StorageBase, StorageOptionDescription
+from .util import load
+
+
+load()
+
+
+__all__ = (setting, Values, Settings, Storage, list_sessions, delete_session,
+           StorageBase, StorageOptionDescription)
+#           Base, OptionDescription)
index 96673ad..a2c103f 100644 (file)
 #
 # ____________________________________________________________
 from tiramisu.i18n import _
-from tiramisu.setting import groups
+from tiramisu.setting import groups, undefined
+from tiramisu.error import ConfigError
+from .util import SqlAlchemyBase
+import util
 
 from sqlalchemy import not_, or_
-from sqlalchemy.ext.declarative import declarative_base, declared_attr
+from sqlalchemy.ext.declarative import declared_attr
 from sqlalchemy.ext.associationproxy import association_proxy
-from sqlalchemy import create_engine, Column, Integer, String, Boolean, \
-    PickleType, ForeignKey, Table
+from sqlalchemy import Column, Integer, String, Boolean, PickleType, \
+    ForeignKey, Table
 from sqlalchemy.orm import relationship, backref
-from sqlalchemy.orm import sessionmaker
 from sqlalchemy.orm.collections import attribute_mapped_collection
 
-
-#FIXME
-engine = create_engine('sqlite:///:memory:')
-SqlAlchemyBase = declarative_base()
-#FIXME a voir:
-#         # Organization.members will be a Query object - no loading
-#         # of the entire collection occurs unless requested
-#         lazy="dynamic",
-#____________________________________________________________
-#
-# require
+from itertools import chain
 
 
 def load_requires(collection_type, proxy):
@@ -49,7 +41,7 @@ def load_requires(collection_type, proxy):
         ret = []
         requires = getattr(obj, proxy.value_attr)
         for require in requires:
-            option = session.query(_Base).filter_by(id=require.option).first()
+            option = util.session.query(_Base).filter_by(id=require.option).first()
             ret.append(tuple([option, require.expected, require.action, require.inverse, require.transitive, require.same_action]))
         return tuple(ret)
 
@@ -158,7 +150,7 @@ def load_callback_parm(collection_type, proxy):
             if require.value is not None:
                 ret.append(require.value)
             else:
-                option = session.query(_Base).filter_by(id=require.option).first()
+                option = util.session.query(_Base).filter_by(id=require.option).first()
                 ret.append((option, require.force_permissive))
         return tuple(ret)
 
@@ -175,10 +167,10 @@ class _CallbackParamOption(SqlAlchemyBase):
     force_permissive = Column(Boolean)
     value = Column(PickleType)
 
-    def __init__(self, option=None, force_permissive=None,  value=None):
-        if value is not None:
+    def __init__(self, option=undefined, force_permissive=undefined,  value=undefined):
+        if value is not undefined:
             self.value = value
-        else:
+        elif option is not undefined:
             self.option = option.id
             self.force_permissive = force_permissive
 
@@ -194,8 +186,11 @@ class _CallbackParam(SqlAlchemyBase):
         self.key = key
         for param in params:
             if isinstance(param, tuple):
-                self.params.append(_CallbackParamOption(option=param[0],
-                                                        force_permissive=param[1]))
+                if param == (None,):
+                    self.params.append(_CallbackParamOption())
+                else:
+                    self.params.append(_CallbackParamOption(option=param[0],
+                                                            force_permissive=param[1]))
             else:
                 self.params.append(_CallbackParamOption(value=param))
 
@@ -215,7 +210,7 @@ class _Consistency(SqlAlchemyBase):
     func = Column(PickleType)
     params = Column(PickleType)
 
-    def __init__(self, func, all_cons_opts):
+    def __init__(self, func, all_cons_opts, params):
         self.func = func
         for option in all_cons_opts:
             option._consistencies.append(self)
@@ -249,9 +244,16 @@ class _Base(SqlAlchemyBase):
     _informations = association_proxy("_infos", "value")
     _default = Column(PickleType)
     _default_multi = Column(PickleType)
+    _subdyn = Column(Integer)
+    _choice_values = Column(PickleType)
+    _cho_params = relationship('_CallbackParam',
+                               collection_class=
+                               attribute_mapped_collection('key'))
+    _choice_params = association_proxy("_cho_params", "params",
+                                       getset_factory=load_callback_parm)
     _reqs = relationship("_Require", collection_class=list)
     _requires = association_proxy("_reqs", "requires", getset_factory=load_requires)
-    _multi = Column(Boolean)
+    _multi = Column(Integer)
     _multitype = Column(String)
     ######
     _callback = Column(PickleType)
@@ -264,7 +266,7 @@ class _Base(SqlAlchemyBase):
     _val_params = relationship('_CallbackParam',
                                collection_class=
                                attribute_mapped_collection('key'))
-    _validator_params = association_proxy("_call_params", "params",
+    _validator_params = association_proxy("_val_params", "params",
                                           getset_factory=load_callback_parm)
     ######
     #FIXME pas 2 fois la meme properties dans la base ...
@@ -290,11 +292,11 @@ class _Base(SqlAlchemyBase):
     _is_build_cache = Column(Boolean, default=False)
 
     def __init__(self):
+        util.session.add(self)
         self.commit()
 
     def commit(self):
-        session.add(self)
-        session.commit()
+        util.session.commit()
 
     def _add_consistency(self, func, all_cons_opts, params):
         _Consistency(func, all_cons_opts, params)
@@ -306,52 +308,35 @@ class _Base(SqlAlchemyBase):
     def _get_id(self):
         return self.id
 
-    # ____________________________________________________________
-    # 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")
-    #    """
-    #    info = session.query(_Information).filter_by(option=self.id, key=key).first()
-    #    #FIXME pas append ! remplacer !
-    #    if info is None:
-    #        self._informations.append(_Information(key, value))
-    #    else:
-    #        info.value = value
-
-    #def impl_get_information(self, key, default=None):
-    #    """retrieves one information's item
-
-    #    :param key: the item string (ex: "help")
-    #    """
-    #    info = session.query(_Information).filter_by(option=self.id, key=key).first()
-    #    if info is not None:
-    #        return info.value
-    #        return self._informations[key]
-    #    elif default is not None:
-    #        return default
-    #    else:
-    #        raise ValueError(_("information's item not found: {0}").format(
-    #            key))
+    def _impl_getsubdyn(self):
+        return self._subdyn
+
+    def _impl_setsubdyn(self, subdyn):
+        self._subdyn = subdyn.id
 
 
 class Cache(SqlAlchemyBase):
     __tablename__ = 'cache'
     id = Column(Integer, primary_key=True)
-    #FIXME indexer ... les 3
-    path = Column(String, nullable=False)
-    descr = Column(Integer, nullable=False)
-    option = Column(Integer, nullable=False)
-    opt_type = Column(String, nullable=False)
-
-    def __init__(self, descr, option, path):
+    path = Column(String, nullable=False, index=True)
+    descr = Column(Integer, nullable=False, index=True)
+    parent = Column(Integer, nullable=False, index=True)
+    option = Column(Integer, nullable=False, index=True)
+    opt_type = Column(String, nullable=False, index=True)
+    is_subdyn = Column(Boolean, nullable=False, index=True)
+    subdyn_path = Column(String)
+
+    def __init__(self, descr, parent, option, path, subdyn_path):
         self.descr = descr.id
+        self.parent = parent.id
         self.option = option.id
         self.path = path
         self.opt_type = option.__class__.__name__
+        #is_subdyn = option._is_subdyn()
+        is_subdyn = option.impl_is_dynoptiondescription()
+        self.is_subdyn = is_subdyn
+        if is_subdyn:
+            self.subdyn_path = subdyn_path
 
 
 class StorageOptionDescription(object):
@@ -359,14 +344,14 @@ class StorageOptionDescription(object):
         return self._is_build_cache
 
     def impl_get_opt_by_path(self, path):
-        ret = session.query(Cache).filter_by(descr=self.id, path=path).first()
+        ret = util.session.query(Cache).filter_by(descr=self.id, path=path).first()
         if ret is None:
             raise AttributeError(_('no option for path {0}').format(path))
-        return session.query(_Base).filter_by(id=ret.option).first()
+        return util.session.query(_Base).filter_by(id=ret.option).first()
 
     def impl_get_path_by_opt(self, opt):
-        ret = session.query(Cache).filter_by(descr=self.id,
-                                             option=opt.id).first()
+        ret = util.session.query(Cache).filter_by(descr=self.id,
+                                                  option=opt.id).first()
         if ret is None:
             raise AttributeError(_('no option {0} found').format(opt))
         return ret.path
@@ -374,30 +359,36 @@ class StorageOptionDescription(object):
     def impl_get_group_type(self):
         return getattr(groups, self._group_type)
 
-    def impl_build_cache_option(self, descr=None, _currpath=None):
+    def impl_build_cache_option(self, descr=None, _currpath=None,
+                                subdyn_path=None):
         if descr is None:
             save = True
             descr = self
             _currpath = []
         else:
             save = False
-        for option in self.impl_getchildren():
+        for option in self._impl_getchildren(dyn=False):
             attr = option.impl_getname()
-            session.add(Cache(descr, option,
-                              str('.'.join(_currpath + [attr]))))
+            util.session.add(Cache(descr, self, option,
+                             str('.'.join(_currpath + [attr])), subdyn_path))
             if isinstance(option, StorageOptionDescription):
+                if option.impl_is_dynoptiondescription():
+                    subdyn_path = '.'.join(_currpath)
                 _currpath.append(attr)
                 option.impl_build_cache_option(descr,
-                                               _currpath)
+                                               _currpath,
+                                               subdyn_path)
                 _currpath.pop()
         if save:
             self._is_build_cache = True
-            session.commit()
+            util.session.commit()
 
-    def impl_get_options_paths(self, bytype, byname, _subpath, only_first):
-        sqlquery = session.query(Cache).filter_by(descr=self.id)
+    def impl_get_options_paths(self, bytype, byname, _subpath, only_first,
+                               context):
+        sqlquery = util.session.query(Cache).filter_by(descr=self.id)
         if bytype is None:
-            sqlquery = sqlquery.filter(not_(Cache.opt_type == 'OptionDescription'))
+            sqlquery = sqlquery.filter(not_(
+                Cache.opt_type == 'OptionDescription'))
         else:
             sqlquery = sqlquery.filter_by(opt_type=bytype.__name__)
 
@@ -413,46 +404,111 @@ class StorageOptionDescription(object):
             if or_query != '':
                 filter_query = or_(Cache.path == or_query, filter_query)
             sqlquery = sqlquery.filter(filter_query)
-        if only_first:
-            opt = sqlquery.first()
-            if opt is None:
-                return tuple()
-            option = session.query(_Base).filter_by(id=opt.option).first()
-            return ((opt.path, option),)
-        else:
-            ret = []
-            for opt in sqlquery.all():
-                option = session.query(_Base).filter_by(id=opt.option).first()
-                ret.append((opt.path, option))
-            return ret
+        #if only_first:
+        #    opt = sqlquery.first()
+        #    if opt is None:
+        #        return tuple()
+        #    option = util.session.query(_Base).filter_by(id=opt.option).first()
+        #    return ((opt.path, option),)
+        #else:
+        ret = []
+        for opt in sqlquery.all():
+            option = util.session.query(_Base).filter_by(id=opt.option).first()
+            if opt.is_subdyn:
+                name = option.impl_getname()
+                if byname is not None and byname.startswith(name):
+                    found = False
+                    for suffix in option._subdyn._impl_get_suffixes(
+                            context):
+                        if byname == name + suffix:
+                            found = True
+                            subdyn_path = opt.subdyn_path
+                            dynpaths = opt.path[len(subdyn_path):].split('.')
+
+                            path = subdyn_path
+                            for dynpath in dynpaths:
+                                path += '.' + dynpath + suffix
+                            option = option._impl_to_dyn(
+                                name + suffix, path)
+                            break
+                    if not found:
+                        break
+            else:
+                ret_opt = (opt.path, option)
+            if only_first:
+                return ret_opt
+            ret.append(ret_opt)
+        return ret
 
     def _add_children(self, child_names, children):
         for child in children:
-            session.add(_Parent(self, child))
-
-    def impl_getchildren(self):
-        for child in session.query(_Parent).filter_by(parent_id=self.id).all():
-            yield(session.query(_Base).filter_by(id=child.child_id).first())
-        #return
-
-    def __getattr__(self, name):
-        if name.startswith('_') or name.startswith('impl_'):
-            return object.__getattribute__(self, name)
-        child = session.query(_Parent).filter_by(parent_id=self.id, child_name=name).first()
-        if child is None:
-            raise AttributeError(_('unknown Option {0} '
-                                   'in OptionDescription {1}'
-                                   '').format(name, self.impl_getname()))
-        return session.query(_Base).filter_by(id=child.child_id).first()
+            util.session.add(_Parent(self, child))
+
+    def _impl_st_getchildren(self, context, only_dyn=False):
+        if only_dyn is False or context is undefined:
+            for child in util.session.query(_Parent).filter_by(
+                    parent_id=self.id).all():
+                yield(util.session.query(_Base).filter_by(id=child.child_id
+                                                          ).first())
+        else:
+            descr = context.cfgimpl_get_description().id
+            for child in util.session.query(Cache).filter_by(descr=descr,
+                                                             parent=self.id
+                                                             ).filter_by(
+                    is_subdyn=True).all():
+                yield(util.session.query(_Base).filter_by(id=child.option).first())
+
+    def _getattr(self, name, suffix=undefined, context=undefined, dyn=True):
+        error = False
+        if suffix is not undefined:
+            try:
+                if undefined in [suffix, context]:  # pragma: optional cover
+                    raise ConfigError(_("suffix and context needed if "
+                                        "it's a dyn option"))
+                if name.endswith(suffix):
+                    oname = name[:-len(suffix)]
+                    #child = self._children[1][self._children[0].index(oname)]
+                    child = util.session.query(_Parent).filter_by(
+                        parent_id=self.id, child_name=oname).first()
+                    if child is None:
+                        error = True
+                    else:
+                        opt = util.session.query(_Base).filter_by(
+                            id=child.child_id).first()
+                        return self._impl_get_dynchild(opt, suffix)
+                else:
+                    error = True
+            except ValueError:  # pragma: optional cover
+                error = True
+        else:
+            child = util.session.query(_Parent).filter_by(parent_id=self.id,
+                                                          child_name=name
+                                                          ).first()
+            if child is None:
+                child = self._impl_search_dynchild(name, context=context)
+                if child != []:
+                    return child
+                error = True
+            if error is False:
+                return util.session.query(_Base).filter_by(id=child.child_id
+                                                           ).first()
+        if error:
+            raise AttributeError(_('unknown Option {0} in OptionDescription {1}'
+                                 '').format(name, self.impl_getname()))
+
+    def _get_force_store_value(self):
+        #only option in current tree
+        current_ids = tuple(chain(*util.session.query(Cache.option).filter_by(
+            descr=self.id).all()))
+        for prop in util.session.query(_PropertyOption).filter(
+                _PropertyOption.option.in_(current_ids),
+                _PropertyOption.name == 'force_store_value').all():
+            opt = util.session.query(_Base).filter_by(id=prop.option).first()
+            path = self.impl_get_path_by_opt(opt)
+            yield (opt, path)
 
 
 class StorageBase(_Base):
     @declared_attr
     def __mapper_args__(self):
         return {'polymorphic_identity': self.__name__.lower()}
-
-
-#engine.echo = True
-SqlAlchemyBase.metadata.create_all(engine)
-Session = sessionmaker(bind=engine)
-session = Session()
index 23dbaae..a6e30be 100644 (file)
@@ -80,8 +80,6 @@ class Values(object):
         # if value has callback and is not set
         if opt.impl_has_callback():
             callback, callback_params = opt.impl_get_callback()
-            if callback_params is None:
-                callback_params = {}
             value = carry_out_calculation(opt, config=self._getcontext(),
                                           callback=callback,
                                           callback_params=callback_params,
@@ -126,8 +124,8 @@ class Values(object):
     def get_modified_values(self):
         context = self._getcontext()
         if context._impl_descr is not None:
-            for opt, path in context.cfgimpl_get_settings(
-            ).get_with_property('force_store_value'):
+            for opt, path in context.cfgimpl_get_description(
+            )._get_force_store_value():
                 self._getowner(opt, path, force_permissive=True)
         return self._p_.get_modified_values()
 
@@ -554,7 +552,6 @@ class Multi(list):
                                                           self)
 
     #def __repr__(self, *args, **kwargs):
-    #    print args, kwargs
     #    return super(Multi, self).__repr__(*args, **kwargs)
 
     #def __getitem__(self, y):