works on sqlalchemy storage
authorEmmanuel Garette <egarette@cadoles.com>
Mon, 10 Nov 2014 08:13:44 +0000 (09:13 +0100)
committerEmmanuel Garette <egarette@cadoles.com>
Mon, 10 Nov 2014 08:13:44 +0000 (09:13 +0100)
12 files changed:
test/test_dereference.py
test/test_dyn_optiondescription.py
test/test_symlink.py
tiramisu/config.py
tiramisu/option/baseoption.py
tiramisu/option/optiondescription.py
tiramisu/storage/dictionary/option.py
tiramisu/storage/sqlalchemy/option.py
tiramisu/storage/sqlalchemy/setting.py
tiramisu/storage/sqlalchemy/storage.py
tiramisu/storage/sqlalchemy/value.py
tiramisu/value.py

index c39bfcb..f153db5 100644 (file)
@@ -7,6 +7,8 @@ from tiramisu.option import BoolOption, IntOption, StrOption, OptionDescription,
 import weakref
 
 
+IS_DEREFABLE = True
+
 def test_deref_storage():
     b = BoolOption('b', '')
     o = OptionDescription('od', '', [b])
@@ -44,16 +46,23 @@ def test_deref_config():
 
 
 def test_deref_option():
+    global IS_DEREFABLE
     b = BoolOption('b', '')
     o = OptionDescription('od', '', [b])
     w = weakref.ref(b)
     del(b)
-    assert w() is not None
+    try:
+        assert w() is not None
+    except AssertionError:
+        IS_DEREFABLE = False
+        return
     del(o)
     assert w() is None
 
 
 def test_deref_optiondescription():
+    if not IS_DEREFABLE:
+        return
     b = BoolOption('b', '')
     o = OptionDescription('od', '', [b])
     w = weakref.ref(o)
@@ -64,6 +73,8 @@ def test_deref_optiondescription():
 
 
 def test_deref_option_cache():
+    if not IS_DEREFABLE:
+        return
     b = BoolOption('b', '')
     o = OptionDescription('od', '', [b])
     o.impl_build_cache_option()
@@ -86,6 +97,8 @@ def test_deref_optiondescription_cache():
 
 
 def test_deref_option_config():
+    if not IS_DEREFABLE:
+        return
     b = BoolOption('b', '')
     o = OptionDescription('od', '', [b])
     c = Config(o)
index cdb6ce1..d5db882 100644 (file)
@@ -27,7 +27,7 @@ def return_dynval(suffix, value='val'):
 
 
 def return_list2(suffix):
-    return [suffix, 'val2']
+    return [str(suffix), 'val2']
 
 
 def return_list(val=None):
index 3283f6d..7f86837 100644 (file)
@@ -17,6 +17,7 @@ def test_symlink_option():
     descr = OptionDescription("opt", "",
                               [linkopt, OptionDescription("s1", "", [boolopt])])
     config = Config(descr)
+    assert config.s1.b is False
     setattr(config, "s1.b", True)
     setattr(config, "s1.b", False)
     assert config.s1.b is False
index 6c95a81..58279ec 100644 (file)
@@ -211,7 +211,7 @@ class SubConfig(object):
         elif isinstance(child, SymLinkOption) and \
                 not isinstance(child, DynSymLinkOption):  # pragma: no dynoptiondescription cover
             path = context.cfgimpl_get_description().impl_get_path_by_opt(
-                child._opt)
+                child._impl_getopt())
             context._setattr(path, value, force_permissive=force_permissive)
         else:
             subpath = self._get_subpath(name)
@@ -253,7 +253,8 @@ class SubConfig(object):
             return homeconfig.getattr(name, force_permissive=force_permissive,
                                       validate=validate)
         context = self._cfgimpl_get_context()
-        opt_or_descr = self.cfgimpl_get_description().__getattr__(name, context=context)
+        opt_or_descr = self.cfgimpl_get_description().__getattr__(
+            name, context=context)
         subpath = self._get_subpath(name)
         if isinstance(opt_or_descr, DynSymLinkOption):
             return self.cfgimpl_get_values()._get_cached_item(
@@ -262,7 +263,7 @@ class SubConfig(object):
                 force_permissive=force_permissive)
         elif isinstance(opt_or_descr, SymLinkOption):  # pragma: no dynoptiondescription cover
             path = context.cfgimpl_get_description().impl_get_path_by_opt(
-                opt_or_descr._opt)
+                opt_or_descr._impl_getopt())
             return context.getattr(path, validate=validate,
                                    force_permissive=force_permissive)
         elif opt_or_descr.impl_is_optiondescription():
@@ -319,6 +320,7 @@ class SubConfig(object):
         :param first: return only one option if True, a list otherwise
         :return: find list or an exception if nothing has been found
         """
+
         def _filter_by_value():
             if byvalue is undefined:
                 return True
@@ -478,7 +480,7 @@ class SubConfig(object):
         descr = self.cfgimpl_get_description()
         if not dyn and descr.impl_is_dynoptiondescription():
             context_descr = self._cfgimpl_get_context().cfgimpl_get_description()
-            return context_descr.impl_get_path_by_opt(descr._opt)
+            return context_descr.impl_get_path_by_opt(descr._impl_getopt())
         return self._impl_path
 
 
@@ -489,8 +491,8 @@ class _CommonConfig(SubConfig):
     def _impl_build_all_caches(self):
         if not self.cfgimpl_get_description().impl_already_build_caches():
             self.cfgimpl_get_description().impl_build_cache_consistency()
-            self.cfgimpl_get_description().impl_validate_options()
             self.cfgimpl_get_description().impl_build_cache_option()
+            self.cfgimpl_get_description().impl_validate_options()
 
     def read_only(self):
         "read only is a global config's setting, see `settings.py`"
@@ -504,7 +506,9 @@ class _CommonConfig(SubConfig):
         """convenience method to retrieve an option's owner
         from the config itself
         """
-        if not isinstance(opt, Option) and not isinstance(opt, SymLinkOption):  # pragma: optional cover
+        if not isinstance(opt, Option) and \
+                not isinstance(opt, SymLinkOption) and \
+                not isinstance(opt, DynSymLinkOption):  # pragma: optional cover
             raise TypeError(_('opt in getowner must be an option not {0}'
                               '').format(type(opt)))
         return self.cfgimpl_get_values().getowner(opt,
index 658b061..74b26ca 100644 (file)
@@ -103,11 +103,11 @@ class Base(StorageBase):
         if not valid_name(name):  # pragma: optional cover
             raise ValueError(_("invalid name: {0} for option").format(name))
         if requires is not None:
-            self._calc_properties, self._requires = validate_requires_arg(
+            calc_properties, requires = validate_requires_arg(
                 requires, name)
-        #else:
-        #    self._calc_properties = frozenset()
-        #    self._requires = []
+        else:
+            calc_properties = frozenset()
+            requires = undefined
         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))
@@ -127,17 +127,18 @@ class Base(StorageBase):
         if validator is not None:
             validate_callback(validator, validator_params, 'validator')
             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 calc_properties != frozenset([]) and properties is not tuple():  # pragma: optional cover
+            set_forbidden_properties = calc_properties & set(properties)
             if set_forbidden_properties != frozenset():
                 raise ValueError('conflict: properties already set in '
                                  'requirement {0}'.format(
                                      list(set_forbidden_properties)))
-        super(Base, self).__init__(name, _multi, warnings_only, doc, extra)
+        StorageBase.__init__(self, name, _multi, warnings_only, doc, extra,
+                             calc_properties, requires, properties)
         self._set_default_values(default, default_multi)
         if callback is not False:
             self.impl_set_callback(callback, callback_params)
-        self._properties = properties
+        self.commit()
 
     def impl_set_callback(self, callback, callback_params=None):
         if callback is None and callback_params is not None:  # pragma: optional cover
@@ -275,12 +276,12 @@ class BaseOption(Base):
         except AttributeError:  # pragma: optional cover
             raise SystemError(_('cannot serialize Option, '
                                 'only in OptionDescription'))
-        slots = set()
-        for subclass in self.__class__.__mro__:
-            if subclass is not object:
-                slots.update(subclass.__slots__)
-        slots -= frozenset(['_cache_paths', '_cache_consistencies',
-                            '__weakref__'])
+        if isinstance(self, SymLinkOption):
+            slots = frozenset(['_name', '_state_opt', '_stated'])
+        else:
+            slots = self._impl_getattributes()
+            slots -= frozenset(['_cache_paths', '_cache_consistencies',
+                                '__weakref__'])
         states = {}
         for slot in slots:
             # remove variable if save variable converted
@@ -338,9 +339,10 @@ class BaseOption(Base):
         """
         if name not in ('_option', '_is_build_cache') \
                 and not isinstance(value, tuple) and \
-                not name.startswith('_state'):
+                not name.startswith('_state') and \
+                not name == '_sa_instance_state':
             is_readonly = False
-            # never change _name
+            # never change _name dans _opt
             if name == '_name':
                 try:
                     if self.impl_getname() is not None:
@@ -348,13 +350,21 @@ class BaseOption(Base):
                         is_readonly = True
                 except (KeyError, AttributeError):
                     pass
+            elif name == '_opt':
+                try:
+                    if self._impl_getopt() is not None:
+                        #so _opt is already set
+                        is_readonly = True
+                except (KeyError, AttributeError):
+                    pass
             elif name != '_readonly':
                 is_readonly = self.impl_is_readonly()
             if is_readonly:  # pragma: optional cover
                 raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
                                        " read-only").format(
                                            self.__class__.__name__,
-                                           self.impl_getname(),
+                                           self,
+                                           #self.impl_getname(),
                                            name))
         super(BaseOption, self).__setattr__(name, value)
 
@@ -844,29 +854,31 @@ def validate_requires_arg(requires, name):
 
 
 class SymLinkOption(OnlyOption):
-    __slots__ = ('_opt', '_state_opt', '_readonly')
+#    __slots__ = ('_opt', '_state_opt')
 
     def __init__(self, name, opt):
         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._set_readonly()
-        super(Base, self).__init__(name, undefined, undefined, undefined, undefined)
+        super(Base, self).__init__(name, undefined, undefined, undefined,
+                                   undefined, undefined, undefined, undefined,
+                                   opt)
+        self.commit()
 
     def __getattr__(self, name, context=undefined):
-        if name in ('_opt', '_opt_type', '_readonly', 'impl_getpath', '_name', '_state_opt'):
+        if name in ('_opt', '_opt_type', '_readonly', 'impl_getpath', '_name',
+                    '_state_opt', '_impl_setopt'):
             return object.__getattr__(self, name)
         else:
-            return getattr(self._opt, name)
+            return getattr(self._impl_getopt(), name)
 
     def _impl_getstate(self, descr):
         self._stated = True
-        self._state_opt = descr.impl_get_path_by_opt(self._opt)
+        self._state_opt = descr.impl_get_path_by_opt(self._impl_getopt())
 
     def _impl_setstate(self, descr):
-        self._opt = descr.impl_get_opt_by_path(self._state_opt)
+        self._impl_setopt(descr.impl_get_opt_by_path(self._state_opt))
         del(self._state_opt)
         try:
             del(self._stated)
@@ -875,46 +887,56 @@ class SymLinkOption(OnlyOption):
         self._set_readonly()
 
     def impl_get_information(self, key, default=undefined):
-        return self._opt.impl_get_information(key, default)
-
-    def _set_readonly(self):
-        self._readonly = True
+        return self._impl_getopt().impl_get_information(key, default)
 
     def impl_is_readonly(self):
-        try:
-            return self._readonly
-        except AttributeError:
-            return False
+        return True
 
     def impl_getproperties(self):
-        return self._opt._properties
+        return self._impl_getopt()._properties
 
     def impl_get_callback(self):
-        return self._opt.impl_get_callback()
+        return self._impl_getopt().impl_get_callback()
 
     def impl_has_callback(self):
         "to know if a callback has been defined or not"
-        return self._opt.impl_has_callback()
+        return self._impl_getopt().impl_has_callback()
+
+    def impl_is_multi(self):
+        return self._impl_getopt().impl_is_multi()
 
     def _is_subdyn(self):
         try:
-            return self._opt._subdyn is not None
+            return self._impl_getopt()._subdyn is not None
         except AttributeError:
             return False
 
 
-class DynSymLinkOption(SymLinkOption):
-    __slots__ = ('_dyn',)
+class DynSymLinkOption(object):
+    __slots__ = ('_dyn', '_opt', '_name')
 
     def __init__(self, name, opt, dyn):
+        self._name = name
         self._dyn = dyn
-        super(DynSymLinkOption, self).__init__(name, opt)
+        self._opt = opt
+
+    def __getattr__(self, name, context=undefined):
+        if name in ('_opt', '_opt_type', '_readonly', 'impl_getpath', '_name', '_state_opt'):
+            return object.__getattr__(self, name)
+        else:
+            return getattr(self._impl_getopt(), name)
+
+    def impl_getname(self):
+        return self._name
+
+    def _impl_getopt(self):
+        return self._opt
 
     def impl_getsuffix(self):
-        return self._dyn.split('.')[-1][len(self._opt.impl_getname()):]
+        return self._dyn.split('.')[-1][len(self._impl_getopt().impl_getname()):]
 
     def impl_getpath(self, context):
-        path = self._opt.impl_getpath(context)
+        path = self._impl_getopt().impl_getpath(context)
         base_path = '.'.join(path.split('.')[:-2])
         if self.impl_is_master_slaves() and base_path is not '':
             base_path = base_path + self.impl_getsuffix()
@@ -925,5 +947,7 @@ class DynSymLinkOption(SymLinkOption):
 
     def impl_validate(self, value, context=undefined, validate=True,
                       force_index=None, force_submulti_index=None):
-        return self._opt.impl_validate(value, context, validate, force_index,
-                                       force_submulti_index, current_opt=self)
+        return self._impl_getopt().impl_validate(value, context, validate,
+                                                 force_index,
+                                                 force_submulti_index,
+                                                 current_opt=self)
index fa8bdcc..0ec8c56 100644 (file)
@@ -168,7 +168,7 @@ class OptionDescription(BaseOption, StorageOptionDescription):
             return True
         #consistencies is something like [('_cons_not_equal', (opt1, opt2))]
         if isinstance(option, DynSymLinkOption):
-            consistencies = self._cache_consistencies.get(option._opt)
+            consistencies = self._cache_consistencies.get(option._impl_getopt())
         else:
             consistencies = self._cache_consistencies.get(option)
         if consistencies is not None:
@@ -177,7 +177,7 @@ class OptionDescription(BaseOption, StorageOptionDescription):
                 #all_cons_opts[0] is the option where func is set
                 if isinstance(option, DynSymLinkOption):
                     subpath = '.'.join(option._dyn.split('.')[:-1])
-                    namelen = len(option._opt.impl_getname())
+                    namelen = len(option._impl_getopt().impl_getname())
                     suffix = option.impl_getname()[namelen:]
                     opts = []
                     for opt in all_cons_opts:
@@ -361,6 +361,9 @@ class SynDynOptionDescription(object):
     def impl_getpaths(self, include_groups=False, _currpath=None):
         return _impl_getpaths(self, include_groups, _currpath)
 
+    def _impl_getopt(self):
+        return self._opt
+
 
 def _impl_getpaths(klass, include_groups, _currpath):
         """returns a list of all paths in klass, recursively
index f58826e..ddad280 100644 (file)
@@ -61,7 +61,8 @@ class StorageBase(object):
                  '__weakref__'
                  )
 
-    def __init__(self, name, multi, warnings_only, doc, extra):
+    def __init__(self, name, multi, warnings_only, doc, extra, calc_properties,
+                 requires, properties, opt=undefined):
         self._name = name
         if doc is not undefined:
             self._informations = {'doc': doc}
@@ -72,6 +73,15 @@ class StorageBase(object):
         if warnings_only is True:
             self._warnings_only = warnings_only
 
+        if calc_properties is not undefined:
+            self._calc_properties = calc_properties
+        if requires is not undefined:
+            self._requires = requires
+        if properties is not undefined:
+            self._properties = properties
+        if opt is not undefined:
+            self._opt = opt
+
     def _set_default_values(self, default, default_multi):
         if self.impl_is_multi() and default is None:
                 default = []
@@ -214,6 +224,9 @@ class StorageBase(object):
     def _impl_getsubdyn(self):
         return self._subdyn
 
+    def _impl_getopt(self):
+        return self._opt
+
     def _set_readonly(self):
         if not self.impl_is_readonly():
             dico = self._informations
@@ -233,6 +246,9 @@ class StorageBase(object):
     def _impl_setsubdyn(self, subdyn):
         self._subdyn = subdyn
 
+    def _impl_setopt(self, opt):
+        self._opt = opt
+
     def _impl_convert_informations(self, descr, load=False):
         if not load:
             infos = self._informations
@@ -255,6 +271,13 @@ class StorageBase(object):
                 self._set_readonly()
             del(self._state_readonly)
 
+    def _impl_getattributes(self):
+        slots = set()
+        for subclass in self.__class__.__mro__:
+            if subclass is not object:
+                slots.update(subclass.__slots__)
+        return slots
+
     def impl_is_readonly(self):
         try:
             return not isinstance(self._informations, dict)
@@ -320,7 +343,10 @@ class StorageOptionDescription(StorageBase):
                  '_group_type', '_is_build_cache', '_state_group_type')
 
     def __init__(self, name, multi, warnings_only, doc, extra):
-        super(StorageOptionDescription, self).__init__(name, multi, warnings_only, doc, None)
+        super(StorageOptionDescription, self).__init__(name, multi,
+                                                       warnings_only, doc,
+                                                       None, undefined,
+                                                       undefined, undefined)
         self._cache_paths = None
 
     def _add_children(self, child_names, children):
index 6578189..b4ea9dc 100644 (file)
@@ -23,7 +23,7 @@ from tiramisu.error import ConfigError
 from .util import SqlAlchemyBase
 import util
 
-from sqlalchemy import not_, or_
+from sqlalchemy import not_, or_, and_, inspect
 from sqlalchemy.ext.declarative import declared_attr
 from sqlalchemy.ext.associationproxy import association_proxy
 from sqlalchemy import Column, Integer, String, Boolean, PickleType, \
@@ -199,6 +199,7 @@ class _CallbackParam(SqlAlchemyBase):
 #
 # consistency
 consistency_table = Table('consistencyopt', SqlAlchemyBase.metadata,
+                          Column('id', Integer, primary_key=True),
                           Column('left_id', Integer, ForeignKey('consistency.id')),
                           Column('right_id', Integer, ForeignKey('baseoption.id'))
                           )
@@ -214,6 +215,7 @@ class _Consistency(SqlAlchemyBase):
         self.func = func
         for option in all_cons_opts:
             option._consistencies.append(self)
+            print type(option._consistencies)
         self.params = params
 
 
@@ -245,6 +247,8 @@ class _Base(SqlAlchemyBase):
     _default = Column(PickleType)
     _default_multi = Column(PickleType)
     _subdyn = Column(Integer)
+    _dyn = Column(String)
+    _opt = Column(Integer)
     _choice_values = Column(PickleType)
     _cho_params = relationship('_CallbackParam',
                                collection_class=
@@ -268,19 +272,18 @@ class _Base(SqlAlchemyBase):
     _validator_params = association_proxy("_val_params", "params",
                                           getset_factory=load_callback_parm)
     ######
-    #FIXME pas 2 fois la meme properties dans la base ...
     #FIXME not autoload
-    #FIXME normalement tuple ... transforme en set !
     _props = relationship("_PropertyOption", collection_class=set)
     _properties = association_proxy("_props", "name")
-    #FIXME fusion avec expected
     _calc_props = relationship("_CalcProperties", collection_class=set)
     _calc_properties = association_proxy("_calc_props", "name")
     _warnings_only = Column(Boolean)
     _readonly = Column(Boolean, default=False)
     _consistencies = relationship('_Consistency', secondary=consistency_table,
-                                  backref=backref('options', enable_typechecks=False))
+                                  backref=backref('options',
+                                                  enable_typechecks=False))
     _type = Column(String(50))
+    _stated = Column(Boolean)
     __mapper_args__ = {
         'polymorphic_identity': 'option',
         'polymorphic_on': _type
@@ -290,9 +293,27 @@ class _Base(SqlAlchemyBase):
     _group_type = Column(String)
     _is_build_cache = Column(Boolean, default=False)
 
-    def __init__(self):
+    #def __init__(self):
+    def __init__(self, name, multi, warnings_only, doc, extra, calc_properties,
+                 requires, properties, opt=undefined):
         util.session.add(self)
-        self.commit()
+        self._name = name
+        if multi is not undefined:
+            self._multi = multi
+        if warnings_only is not undefined:
+            self._warnings_only = warnings_only
+        if doc is not undefined:
+            self._informations = {'doc': doc}
+        if opt is not undefined:
+            self._opt = opt.id
+        if extra is not undefined:
+            self._extra = extra
+        if calc_properties is not undefined:
+            self._calc_properties = calc_properties
+        if requires is not undefined:
+            self._requires = requires
+        if properties is not undefined:
+            self._properties = properties
 
     def commit(self):
         util.session.commit()
@@ -300,6 +321,15 @@ class _Base(SqlAlchemyBase):
     def _add_consistency(self, func, all_cons_opts, params):
         _Consistency(func, all_cons_opts, params)
 
+    def _set_default_values(self, default, default_multi):
+        if self.impl_is_multi() and default is None:
+                default = []
+        self.impl_validate(default)
+        self._default = default
+        if self.impl_is_multi() and default_multi is not None:
+            self._validate(default_multi)
+            self._default_multi = default_multi
+
     def _get_consistencies(self):
         return [(consistency.func, consistency.options, consistency.params)
                 for consistency in self._consistencies]
@@ -307,11 +337,109 @@ class _Base(SqlAlchemyBase):
     def _get_id(self):
         return self.id
 
+    def impl_get_callback(self):
+        ret = self._callback
+        if ret is None:
+            return (None, {})
+        return ret, self._callback_params
+
+    def impl_get_validator(self):
+        ret = self._validator
+        if ret is None:
+            return (None, {})
+        return ret, self._validator_params
+
     def _impl_getsubdyn(self):
-        return self._subdyn
+        return util.session.query(_Base).filter_by(id=self._subdyn).first()
+
+    def _impl_getopt(self):
+        return util.session.query(_Base).filter_by(id=self._opt).first()
+
+    def impl_getname(self):
+        return self._name
+
+    def impl_getrequires(self):
+        return self._requires
+
+    def impl_getdefault(self):
+        ret = self._default
+        if self.impl_is_multi():
+            if ret is None:
+                return []
+            return list(ret)
+        return ret
+
+    def impl_getdefault_multi(self):
+        if self.impl_is_multi():
+            return self._default_multi
+
+    def _get_extra(self, key):
+        return self._extra[key]
+
+    def _impl_setopt(self, opt):
+        self._opt = opt.id
 
     def _impl_setsubdyn(self, subdyn):
         self._subdyn = subdyn.id
+        self.commit()
+
+    def _set_readonly(self):
+        self._readonly = True
+
+    def _set_callback(self, callback, callback_params):
+        self._callback = callback
+        if callback_params is not None:
+            self._callback_params = callback_params
+
+    def _set_validator(self, validator, validator_params):
+        self._validator = validator
+        if validator_params is not None:
+            self._validator_params = validator_params
+
+    def impl_is_readonly(self):
+        try:
+            return self._readonly
+        except AttributeError:
+            return False
+
+    def impl_is_multi(self):
+        return self._multi == 0 or self._multi == 2
+
+    def impl_is_submulti(self):
+        return self._multi == 2
+
+    def _is_warnings_only(self):
+        return self._warnings_only
+
+    def impl_get_calc_properties(self):
+        try:
+            return self._calc_properties
+        except AttributeError:
+            return frozenset()
+
+    # information
+    def impl_set_information(self, key, value):
+        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 default is not undefined:
+            return self._informations.get(key, default)
+        try:
+            return self._informations[key]
+        except KeyError:  # pragma: optional cover
+            raise ValueError(_("information's item not found: {0}").format(
+                key))
+
+    def _impl_getattributes(self):
+        slots = set()
+        mapper = inspect(self)
+        for column in mapper.attrs:
+                slots.add(column.key)
+        return slots
 
 
 class Cache(SqlAlchemyBase):
@@ -326,16 +454,18 @@ class Cache(SqlAlchemyBase):
     subdyn_path = Column(String)
 
     def __init__(self, descr, parent, option, path, subdyn_path):
+        #context
         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:
+        if subdyn_path:
+            self.is_subdyn = True
             self.subdyn_path = subdyn_path
+        else:
+            self.is_subdyn = False
+            self.subdyn_path = None
 
 
 class StorageOptionDescription(object):
@@ -368,36 +498,59 @@ class StorageOptionDescription(object):
             save = False
         for option in self._impl_getchildren(dyn=False):
             attr = option.impl_getname()
-            util.session.add(Cache(descr, self, option,
-                             str('.'.join(_currpath + [attr])), subdyn_path))
             if isinstance(option, StorageOptionDescription):
+                sub = subdyn_path
                 if option.impl_is_dynoptiondescription():
-                    subdyn_path = '.'.join(_currpath)
+                    sub = '.'.join(_currpath)
+                util.session.add(Cache(descr, self, option,
+                                       str('.'.join(_currpath + [attr])),
+                                       sub))
                 _currpath.append(attr)
                 option.impl_build_cache_option(descr,
                                                _currpath,
-                                               subdyn_path)
+                                               sub)
                 _currpath.pop()
+            else:
+                if subdyn_path:
+                    subdyn_path = '.'.join(_currpath)
+                util.session.add(Cache(descr, self, option,
+                                       str('.'.join(_currpath + [attr])),
+                                       subdyn_path))
         if save:
             self._is_build_cache = True
             util.session.commit()
 
     def impl_get_options_paths(self, bytype, byname, _subpath, only_first,
                                context):
+        def _build_ret_opt(opt, option, suffix, name):
+            subdyn_path = opt.subdyn_path
+            dynpaths = opt.path[len(subdyn_path):].split('.')
+
+            path = subdyn_path
+            dot = False
+            for dynpath in dynpaths:
+                if dot:
+                    path += '.'
+                path += dynpath + suffix
+                dot = True
+            _opt = option._impl_to_dyn(name + suffix, path)
+            return (path, _opt)
+
         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(and_(not_(
+                Cache.opt_type == 'OptionDescription'),
+                not_(Cache.opt_type == 'DynOptionDescription')))
         else:
             sqlquery = sqlquery.filter_by(opt_type=bytype.__name__)
 
         query = ''
         or_query = ''
         if _subpath is not None:
-            query += _subpath + '.'
-        if byname is not None:
-            or_query = query + byname
-            query += '%.' + byname
+            query += _subpath + '.%'
+        #if byname is not None:
+        #    or_query = query + byname
+        #    query += '%.' + byname
         if query != '':
             filter_query = Cache.path.like(query)
             if or_query != '':
@@ -415,28 +568,41 @@ class StorageOptionDescription(object):
             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
+                if byname is not None:
+                    if byname.startswith(name):
+                        found = False
+                        dynoption = option._impl_getsubdyn()
+                        for suffix in dynoption._impl_get_suffixes(
+                                context):
+                            if byname == name + suffix:
+                                found = True
+                                break
+                        if not found:
+                            continue
+                        ret_opt = _build_ret_opt(opt, option, suffix, name)
+                    else:
+                        ret_opt = _build_ret_opt(opt, option, suffix, name)
+                else:
+                    if not only_first:
+                        ret_opt = []
+                    dynoption = option._impl_getsubdyn()
+                    for suffix in dynoption._impl_get_suffixes(context):
+                        val = _build_ret_opt(opt, option, suffix, name)
+                        if only_first:
+                            ret_opt = val
+                        else:
+                            ret_opt.append(val)
             else:
+                if byname is not None and byname != option.impl_getname():
+                    continue
                 ret_opt = (opt.path, option)
             if only_first:
                 return ret_opt
-            ret.append(ret_opt)
+            if isinstance(ret_opt, list):
+                if ret_opt != []:
+                    ret.extend(ret_opt)
+            else:
+                ret.append(ret_opt)
         return ret
 
     def _add_children(self, child_names, children):
@@ -453,8 +619,7 @@ class StorageOptionDescription(object):
             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():
+                                                             ).all():
                 yield(util.session.query(_Base).filter_by(id=child.option).first())
 
     def _getattr(self, name, suffix=undefined, context=undefined, dyn=True):
index c9ad3da..6095979 100644 (file)
@@ -65,12 +65,13 @@ class Settings(Cache, SqlAlchemyBase):
                           cascade="all, delete-orphan")
     _permissives = association_proxy("_perms", "permissives")
 
-    def __init__(self, storage):
-        super(Settings, self).__init__(storage)
+    #def __init__(self, storage):
+    #    super(Settings, self).__init__(storage)
 
     # properties
     def setproperties(self, path, properties):
         self._properties[path] = properties
+        util.session.commit()
 
     def getproperties(self, path, default_properties):
         return self._properties.get(path, set(default_properties))
@@ -80,10 +81,12 @@ class Settings(Cache, SqlAlchemyBase):
 
     def reset_all_properties(self):
         self._properties.clear()
+        util.session.commit()
 
     def delproperties(self, path):
         try:
             del(self._properties[path])
+            util.session.commit()
         except KeyError:
             pass
 
index 252d29e..f64a361 100644 (file)
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 # ____________________________________________________________
 from tiramisu.i18n import _
-from tiramisu.error import ConfigError
 from ..util import SerializeObject
+from .util import SqlAlchemyBase
+import util
+from sqlalchemy import Column, Integer, String
 
 
 class Setting(SerializeObject):
-    """Dictionary storage has no particular setting.
+    """:param extension: database file extension (by default: db)
+    :param dir_database: root database directory (by default: /tmp)
     """
-    pass
+    #FIXME
+    extension = 'db'
+    dir_database = '/tmp'
 
 
 setting = Setting()
-_list_sessions = []
+
+
+class Session(SqlAlchemyBase):
+    __tablename__ = 'session'
+    id = Column(Integer, primary_key=True)
+    session = Column(String, index=True)
+
+    def __init__(self, session_id):
+        self.session = session_id
 
 
 def list_sessions():  # pragma: optional cover
-    return _list_sessions
+    ret = []
+    for val in util.session.query(Session).all():
+        ret.append(val.session)
+    return ret
 
 
 def delete_session(session_id):  # pragma: optional cover
-    raise ConfigError(_('dictionary storage cannot delete session'))
+    #Must remove all values for this session!
+    util.session.delete(util.session.query(Session).filter_by(session=session_id).first())
+    util.session.commit()
 
 
 class Storage(object):
     __slots__ = ('session_id', 'persistent')
-    storage = 'dictionary'
+    storage = 'sqlalchemy'
     #if object could be serializable
     serializable = True
 
     def __init__(self, session_id, persistent, test=False):
-        if not test and session_id in _list_sessions:  # pragma: optional cover
+        if util.session.query(Session).filter_by(session=session_id).first():  # pragma: optional cover
             raise ValueError(_('session already used'))
-        if persistent:  # pragma: optional cover
-            raise ValueError(_('a dictionary cannot be persistent'))
         self.session_id = session_id
         self.persistent = persistent
-        _list_sessions.append(self.session_id)
+        util.session.add(Session(session_id))
+        util.session.commit()
 
     def __del__(self):
-        try:
-            _list_sessions.remove(self.session_id)
-        except AttributeError:  # pragma: optional cover
-            pass
+        delete_session(self.session_id)
index 30447eb..112d3a5 100644 (file)
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 # ____________________________________________________________
 
-#FIXME : il me faut une classe pour le owner !
-#FIXME : pas si simple que ca ... parce que on lit un owner pour une config ...
-#FIXME : mais ca serait peut etre logique
-#FIXME : c'est en fait dans le Setting qu'il faut faire ca ... a voir apr├Ęs
-
 
 from ..util import Cache
 from .util import SqlAlchemyBase
-from sqlalchemy import Column, Integer, String, PickleType, ForeignKey
-from sqlalchemy.orm import relationship
-from sqlalchemy.orm.collections import attribute_mapped_collection
-from sqlalchemy.ext.associationproxy import association_proxy
+import util
+from sqlalchemy import Column, Integer, String, PickleType
+from tiramisu.setting import owners
 
 
 #____________________________________________________________
@@ -36,90 +30,114 @@ from sqlalchemy.ext.associationproxy import association_proxy
 class _Vinformation(SqlAlchemyBase):
     __tablename__ = 'vinformation'
     id = Column(Integer, primary_key=True)
-    values = Column(Integer, ForeignKey('values.id'))
+    session = Column(String, index=True)
+    path = Column(String, index=True)
     key = Column(String)
     value = Column(PickleType)
 
-    def __init__(self, key, value):
+    def __init__(self, session, key, value):
+        self.session = session
         self.key = key
         self.value = value
 
 
-class _Value(SqlAlchemyBase):
+class Value(SqlAlchemyBase):
     __tablename__ = 'value'
     id = Column(Integer, primary_key=True)
-    values = Column(Integer, ForeignKey('values.id'), nullable=False)
-    path = Column(String, nullable=True, unique=True, index=True)
-    #FIXME a revoir avec le owner dans le setting
-    owner = Column(String, nullable=False)
-    value = Column(PickleType, nullable=False)
+    session = Column(String, index=True)
+    path = Column(String, index=True)
+    key = Column(String)
+    value = Column(PickleType)
+    owner = Column(String)
 
-    def __init__(self, key, value):
-        self.path = key
-        self.value = value[0]
-        self.owner = value[1]
+    def __init__(self, session, path, value, owner):
+        self.session = session
+        self.path = path
+        self.value = value
+        self.owner = owner
 
 
-class Values(Cache, SqlAlchemyBase):
-    __tablename__ = 'values'
-    id = Column(Integer, primary_key=True)
-    _vals = relationship("_Value",
-                          collection_class=attribute_mapped_collection('key'),
-                          cascade="all, delete-orphan")
-    _informations = association_proxy("_vals", "value")
-    _infos = relationship("_Vinformation",
-                          collection_class=attribute_mapped_collection('key'),
-                          cascade="all, delete-orphan")
-    _informations = association_proxy("_infos", "value")
-
-    def __init__(self, storage):
-        """init plugin means create values storage
-        """
-        self._values = {}
-        self._informations = {}
-        super(Values, self).__init__(storage)
+class Values(Cache):
 
     # value
     def setvalue(self, path, value, owner):
         """set value for a path
         a specified value must be associated to an owner
         """
-        self._values[path] = (owner, value)
+        val = util.session.query(Value).filter_by(
+            path=path, session=self._storage.session_id).first()
+        if val is None:
+            util.session.add(Value(self._storage.session_id, path, value,
+                                   owner))
+        else:
+            val.value = value
+            val.owner = owner
+        util.session.commit()
 
     def getvalue(self, path):
         """get value for a path
         return: only value, not the owner
         """
-        return self._values[path][1]
+        val = util.session.query(Value).filter_by(
+            path=path, session=self._storage.session_id).first()
+        if not val:
+            raise KeyError('no value found')
+        return val.value
 
     def hasvalue(self, path):
         """if path has a value
         return: boolean
         """
-        return path in self._values
+        return util.session.query(Value).filter_by(
+            path=path, session=self._storage.session_id).first() is not None
 
     def resetvalue(self, path):
         """remove value means delete value in storage
         """
-        del(self._values[path])
+        val = util.session.query(Value).filter_by(
+            path=path, session=self._storage.session_id).first()
+        if val is not None:
+            util.session.delete(val)
+            util.session.commit()
 
     def get_modified_values(self):
         """return all values in a dictionary
         example: {'path1': (owner, 'value1'), 'path2': (owner, 'value2')}
         """
-        return self._values
+        ret = {}
+        for val in util.session.query(Value).filter_by(
+                session=self._storage.session_id).all():
+            ret[val.path] = (val.owner, val.value)
+        return ret
 
     # owner
     def setowner(self, path, owner):
         """change owner for a path
         """
-        self._values[path] = (owner, self._values[path][1])
+        val = util.session.query(Value).filter_by(
+            path=path, session=self._storage.session_id).first()
+        if val is None:
+            raise KeyError('no value found')
+        else:
+            val.owner = owner
+        util.session.commit()
 
     def getowner(self, path, default):
         """get owner for a path
         return: owner object
         """
-        return self._values.get(path, (default, None))[0]
+        val = util.session.query(Value).filter_by(
+            path=path, session=self._storage.session_id).first()
+        if val is None:
+            return default
+        else:
+            owner = val.owner
+            # autocreate owners
+            try:
+                return getattr(owners, owner)
+            except AttributeError:
+                owners.addowner(owner)
+                return getattr(owners, owner)
 
     def set_information(self, key, value):
         """updates the information's attribute
@@ -128,14 +146,23 @@ class Values(Cache, SqlAlchemyBase):
         :param key: information's key (ex: "help", "doc"
         :param value: information's value (ex: "the help string")
         """
-        self._informations[key] = value
+        pass
+        val = util.session.query(_Vinformation).filter_by(
+            key=key, session=self._storage.session_id).first()
+        if val is None:
+            util.session.add(_Vinformation(self._storage.session_id, key,
+                                           value))
+        else:
+            val.value = value
+        util.session.commit()
 
     def get_information(self, key):
         """retrieves one information's item
 
         :param key: the item string (ex: "help")
         """
-        if key in self._informations:
-            return self._informations[key]
-        else:  # pragma: optional cover
+        val = util.session.query(_Vinformation).filter_by(
+            key=key, session=self._storage.session_id).first()
+        if not val:
             raise ValueError("not found")
+        return val.value
index 11f6d79..fb2e36d 100644 (file)
@@ -346,7 +346,7 @@ class Values(object):
         """
         if isinstance(opt, SymLinkOption) and \
                 not isinstance(opt, DynSymLinkOption):
-            opt = opt._opt
+            opt = opt._impl_getopt()
         path = opt.impl_getpath(self._getcontext())
         return self._getowner(opt, path, force_permissive=force_permissive)