require works well in sqlalchemy storage
authorEmmanuel Garette <egarette@cadoles.com>
Mon, 27 Jan 2014 16:16:05 +0000 (17:16 +0100)
committerEmmanuel Garette <egarette@cadoles.com>
Mon, 27 Jan 2014 16:16:05 +0000 (17:16 +0100)
test/test_option_setting.py
tiramisu/option.py
tiramisu/setting.py
tiramisu/storage/sqlalchemy/option.py

index 044b61b..e857a8b 100644 (file)
@@ -311,10 +311,10 @@ def test_append_properties():
     cfg = Config(descr)
     setting = cfg.cfgimpl_get_settings()
     option = cfg.cfgimpl_get_description().gc.dummy
-    assert tuple(option.impl_getproperties()) == tuple()
+    assert tuple(option._properties) == tuple()
     assert not 'test' in setting[option]
     setting[option].append('test')
-    assert tuple(option.impl_getproperties()) == tuple()
+    assert tuple(option._properties) == tuple()
     assert 'test' in setting[option]
 
 
index 014be90..587772d 100644 (file)
@@ -67,11 +67,9 @@ class Base(StorageBase):
             raise ValueError(_("invalid name: {0} for option").format(name))
         self._name = name
         self.impl_set_information('doc', doc)
-        requires = validate_requires_arg(requires, self._name)
         if requires is not None:
-            for values in requires.values():
-                for require in values.values():
-                    self._add_require(require)
+            self._calc_properties, self._requires = validate_requires_arg(
+                requires, self._name)
         if not multi and default_multi is not None:
             raise ValueError(_("a default_multi is set whereas multi is False"
                              " in option: {0}").format(name))
@@ -118,8 +116,8 @@ class Base(StorageBase):
             if callback_params is not None:
                 for key, values in callback_params.items():
                     self._add_callback(key, values)
-        if requires is not None and properties is not tuple():
-            set_forbidden_properties = set(properties) & set(requires.keys())
+        if self._calc_properties != frozenset([]) and properties is not tuple():
+            set_forbidden_properties = self._calc_properties & set(properties)
             if set_forbidden_properties != frozenset():
                 raise ValueError('conflict: properties already set in '
                                  'requirement {0}'.format(
@@ -133,8 +131,9 @@ class Base(StorageBase):
             self._default = []
         else:
             self._default = default
-        for prop in properties:
-            self._properties.append(self._get_property_object(prop))
+        self._properties = properties
+        #for prop in properties:
+            #self._properties.append(self._get_property_object(prop))
         self._warnings_only = warnings_only
         return super(Base, self).__init__()
 
@@ -148,6 +147,29 @@ class BaseOption(Base):
     #             '_calc_properties', '_impl_informations',
     #             '_state_readonly', '_state_requires', '_stated')
 
+    # information
+    def impl_set_information(self, key, value):
+        """updates the information's attribute
+        (which is a dictionary)
+
+        :param key: information's key (ex: "help", "doc"
+        :param value: information's value (ex: "the help string")
+        """
+        self._informations[key] = value
+
+    def impl_get_information(self, key, default=None):
+        """retrieves one information's item
+
+        :param key: the item string (ex: "help")
+        """
+        if key in self._informations:
+            return self._informations[key]
+        elif default is not None:
+            return default
+        else:
+            raise ValueError(_("information's item not found: {0}").format(
+                key))
+
     # ____________________________________________________________
     # serialize object
     def _impl_convert_requires(self, descr, load=False):
@@ -349,9 +371,9 @@ class Option(BaseOption):
     #                                       name))
     #    object.__setattr__(self, name, value)
 
-    def impl_getproperties(self):
-        for prop in self._properties:
-            yield(prop.name)
+    #def impl_getproperties(self):
+    #    for prop in self._properties:
+    #        yield(prop.name)
 
     def impl_getrequires(self):
         return self._requires
@@ -1161,10 +1183,10 @@ class OptionDescription(BaseOption, StorageOptionDescription):
         # the group_type is useful for filtering OptionDescriptions in a config
         self._optiondescription_group_type = groups.default
 
-    def impl_getproperties(self):
-        #FIXME
-        for prop in self._properties:
-            yield(prop.name)
+    #def impl_getproperties(self):
+    #    #FIXME
+    #    for prop in self._properties:
+    #        yield(prop.name)
 
     def impl_getrequires(self):
         #FIXME
@@ -1223,14 +1245,16 @@ class OptionDescription(BaseOption, StorageOptionDescription):
         #for child in self._children:
         #    yield(session.query(child._type).filter_by(id=child.id).first())
 
-    def impl_build_cache_consistency(self, _consistencies=None):
+    def impl_build_cache_consistency(self, _consistencies=None, cache_option=None):
         #FIXME cache_option !
         if _consistencies is None:
             init = True
             _consistencies = {}
+            cache_option = []
         else:
             init = False
         for option in self.impl_getchildren():
+            cache_option.append(option.id)
             if not isinstance(option, OptionDescription):
                 for consistency in option._consistencies:
                     func = consistency.func
@@ -1240,15 +1264,15 @@ class OptionDescription(BaseOption, StorageOptionDescription):
                                                   []).append((func,
                                                              all_cons_opts))
             else:
-                option.impl_build_cache_consistency(_consistencies)
+                option.impl_build_cache_consistency(_consistencies, cache_option)
         if init and _consistencies != {}:
             self._cache_consistencies = {}
             for opt, cons in _consistencies.items():
                 #FIXME dans le cache ...
-                #if opt.id not in cache_option:
-                #    raise ConfigError(_('consistency with option {0} '
-                #                        'which is not in Config').format(
-                #                            opt.impl_getname()))
+                if opt.id not in cache_option:
+                    raise ConfigError(_('consistency with option {0} '
+                                        'which is not in Config').format(
+                                            opt.impl_getname()))
                 self._cache_consistencies[opt] = tuple(cons)
 
     def impl_validate_options(self, cache_option=None):
@@ -1509,15 +1533,15 @@ def validate_requires_arg(requires, name):
                                             inverse, transitive, same_action)
         else:
             ret_requires[action][option][1].append(expected)
-    ## transform dict to tuple
-    #ret = []
-    #for opt_requires in ret_requires.values():
-    #    ret_action = []
-    #    for require in opt_requires.values():
-    #        ret_action.append((require[0], tuple(require[1]), require[2],
-    #                           require[3], require[4], require[5]))
-    #    ret.append(tuple(ret_action))
-    return ret_requires
+    # transform dict to tuple
+    ret = []
+    for opt_requires in ret_requires.values():
+        ret_action = []
+        for require in opt_requires.values():
+            ret_action.append((require[0], tuple(require[1]), require[2],
+                               require[3], require[4], require[5]))
+        ret.append(tuple(ret_action))
+    return frozenset(config_action.keys()), tuple(ret)
 
 
 def validate_callback(callback, callback_params, type_):
index b491d47..4d7da51 100644 (file)
@@ -385,7 +385,7 @@ class Settings(object):
                 if is_cached:
                     return props
             #FIXME
-            props = self._p_.getproperties(path, opt.impl_getproperties())
+            props = self._p_.getproperties(path, opt._properties)
             if is_apply_req:
                 props |= self.apply_requires(opt, path)
             if 'cache' in self:
@@ -589,44 +589,45 @@ class Settings(object):
         :param path: the option's path in the config
         :type path: str
         """
-        if opt.impl_getrequires() is None:
+        if opt._requires is None:
             return frozenset()
 
         # filters the callbacks
         calc_properties = set()
         context = self._getcontext()
-        for require in opt.impl_getrequires():
-            expected = tuple(require.get_expected())
-            inverse = require.inverse
-            option = require.option
-            reqpath = self._get_path_by_opt(option)
-            if reqpath == path or reqpath.startswith(path + '.'):
-                raise RequirementError(_("malformed requirements "
-                                         "imbrication detected for option:"
-                                         " '{0}' with requirement on: "
-                                         "'{1}'").format(path, reqpath))
-            try:
-                value = context._getattr(reqpath,
-                                                force_permissive=True)
-            except PropertiesOptionError as err:
-                if not require.transitive:
-                    continue
-                properties = err.proptype
-                if require.same_action and require.action not in properties:
-                    raise RequirementError(_("option '{0}' has "
-                                             "requirement's property "
-                                             "error: "
-                                             "{1} {2}").format(opt.impl_getname(),
-                                                               reqpath,
-                                                               properties))
-                # transitive action, force expected
-                value = expected[0]
-                inverse = False
-            if not inverse and value in expected or \
-                    inverse and value not in expected:
-                calc_properties.add(require.action)
-                # the calculation cannot be carried out
-                #break
+        for requires in opt._requires:
+            for require in requires:
+                option, expected, action, inverse, \
+                    transitive, same_action = require
+                reqpath = self._get_path_by_opt(option)
+                if reqpath == path or reqpath.startswith(path + '.'):
+                    raise RequirementError(_("malformed requirements "
+                                             "imbrication detected for option:"
+                                             " '{0}' with requirement on: "
+                                             "'{1}'").format(path, reqpath))
+                try:
+                    value = context._getattr(reqpath,
+                                                    force_permissive=True)
+                except PropertiesOptionError as err:
+                    if not transitive:
+                        continue
+                    properties = err.proptype
+                    if same_action and action not in properties:
+                        raise RequirementError(_("option '{0}' has "
+                                                 "requirement's property "
+                                                 "error: "
+                                                 "{1} {2}").format(opt._name,
+                                                                   reqpath,
+                                                                   properties))
+                    # transitive action, force expected
+                    value = expected[0]
+                    inverse = False
+                if (not inverse and
+                        value in expected or
+                        inverse and value not in expected):
+                    calc_properties.add(action)
+                    # the calculation cannot be carried out
+                    break
         return calc_properties
 
     def _get_path_by_opt(self, opt):
index 54c3256..1b7fbca 100644 (file)
 from tiramisu.i18n import _
 
 from sqlalchemy.ext.declarative import declarative_base, declared_attr
+from sqlalchemy.ext.associationproxy import association_proxy
 from sqlalchemy import create_engine, 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
@@ -39,68 +41,91 @@ SqlAlchemyBase = declarative_base()
 
 
 #_Base : object dans la base de donnée
-# => _RequireOption => il y a une liste d'espect dans _RequireExpected
 # => _PropertyOption => liste des propriétés
-# => _Information => dictionnaire avec clef valeur
 # => _CallbackParam avec des Options
-require_table = Table('require', SqlAlchemyBase.metadata,
-                      Column('left_id', Integer, ForeignKey('requireoption.id')),
-                      Column('right_id', Integer, ForeignKey('baseoption.id'))
-                      )
-
-
-class _RequireExpected(SqlAlchemyBase):
-    __tablename__ = 'expected'
+def load_requires(collection_type, proxy):
+    def getter(obj):
+        if obj is None:
+            return None
+        ret = []
+        requires = getattr(obj, proxy.value_attr)
+        for require in requires:
+            option = 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)
+
+    def setter(obj, value):
+        setattr(obj, proxy.value_attr, value)
+    return getter, setter
+
+
+class _Require(SqlAlchemyBase):
+    __tablename__ = "require"
     id = Column(Integer, primary_key=True)
-    expected = Column(PickleType)
-    require = Column(Integer, ForeignKey('requireoption.id'))
+    requires_id = Column(Integer, ForeignKey("baseoption.id"), nullable=False)
+    requires = relationship('_RequireOption')
 
-    def __init__(self, expected):
-        self.expected = expected
+    def __init__(self, requires):
+        for require in requires:
+            self.requires.append(_RequireOption(require))
 
 
 class _RequireOption(SqlAlchemyBase):
     __tablename__ = 'requireoption'
     id = Column(Integer, primary_key=True)
-    option = relationship('_Base', lazy='joined', cascade="all, delete-orphan")
-    #option = relationship('_Base')
-    expected = relationship("_RequireExpected")
+    require_id = Column(Integer, ForeignKey("require.id"), nullable=False)
+    option = Column(Integer, nullable=False)
+    _expected = relationship("_RequireExpected", collection_class=list,
+                             cascade="all, delete-orphan")
+    expected = association_proxy("_expected", "expected")
+    #expected = Column(String)
     action = Column(String, nullable=False)
     inverse = Column(Boolean, default=False)
     transitive = Column(Boolean, default=True)
     same_action = Column(Boolean, default=True)
 
-    def __init__(self, option, expected, action, inverse, transitive,
-                 same_action):
-        #self.r_opt = option.id
-        self.option = option
-        for expect in expected:
-            self.expected.append(_RequireExpected(expect))
+    def __init__(self, values):
+        option, expected, action, inverse, transitive, same_action = values
+        self.option = option.id
+        self.expected = expected
         self.action = action
         self.inverse = inverse
         self.transitive = transitive
         self.same_action = same_action
 
-    def get_expected(self):
-        for expected in self.expected:
-            yield(expected.expected)
 
-    #def get_option(self, config):
-    #    return config.cfgimpl_get_description().impl_get_opt_by_id(self.r_opt)
+class _RequireExpected(SqlAlchemyBase):
+    __tablename__ = 'expected'
+    id = Column(Integer, primary_key=True)
+    require = Column(Integer, ForeignKey('requireoption.id'), nullable=False)
+    expected = Column(PickleType)
+
+    def __init__(self, expected):
+        #FIXME ne pas creer plusieurs fois la meme _expected_
+        #FIXME pareil avec calc_properties
+        self.expected = expected
+
+
+class _CalcProperties(SqlAlchemyBase):
+    __tablename__ = 'calcproperty'
+    id = Column(Integer, primary_key=True)
+    require = Column(Integer, ForeignKey('baseoption.id'), nullable=False)
+    name = Column(PickleType)
+
+    def __init__(self, name):
+        #FIXME ne pas creer plusieurs fois la meme _expected_
+        #FIXME pareil avec calc_properties
+        self.name = name
 
 
 #____________________________________________________________
 #
 # properties
-property_table = Table('property', SqlAlchemyBase.metadata,
-                       Column('left_id', Integer, ForeignKey('propertyoption.name')),
-                       Column('right_id', Integer, ForeignKey('baseoption.id'))
-                       )
-
-
 class _PropertyOption(SqlAlchemyBase):
     __tablename__ = 'propertyoption'
-    name = Column(String, primary_key=True)
+    id = Column(Integer, primary_key=True)
+    option = Column(Integer, ForeignKey('baseoption.id'), nullable=False)
+    name = Column(String)
 
     def __init__(self, name):
         self.name = name
@@ -112,7 +137,7 @@ class _PropertyOption(SqlAlchemyBase):
 class _Information(SqlAlchemyBase):
     __tablename__ = 'information'
     id = Column(Integer, primary_key=True)
-    option = Column(Integer, ForeignKey('baseoption.id'))
+    option = Column(Integer, ForeignKey('baseoption.id'), nullable=False)
     key = Column(String)
     value = Column(PickleType)
 
@@ -184,11 +209,15 @@ class _Base(SqlAlchemyBase):
     __tablename__ = 'baseoption'
     id = Column(Integer, primary_key=True)
     _name = Column(String)
-    _informations = relationship('_Information')
+    #FIXME not autoload
+    _infos = relationship("_Information",
+                          collection_class=attribute_mapped_collection('key'),
+                          cascade="all, delete-orphan")
+    _informations = association_proxy("_infos", "value")
     _default = Column(PickleType)
     _default_multi = Column(PickleType)
-    _requires = relationship('_RequireOption', secondary=require_table,
-                             backref=backref('self_option', enable_typechecks=False))
+    _reqs = relationship("_Require", collection_class=list)
+    _requires = association_proxy("_reqs", "requires", getset_factory=load_requires)
     _multi = Column(Boolean)
     _multitype = Column(String)
     _callback = Column(PickleType)
@@ -197,8 +226,14 @@ class _Base(SqlAlchemyBase):
     _validator_params = relationship('_CallbackParam')
     _parent = Column(Integer, ForeignKey('baseoption.id'))
     _children = relationship('BaseOption', enable_typechecks=False)
-    _properties = relationship('_PropertyOption', secondary=property_table,
-                               backref=backref('options', enable_typechecks=False))
+    #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,
@@ -206,7 +241,6 @@ class _Base(SqlAlchemyBase):
     _choice_values = Column(PickleType)
     _choice_open_values = Column(Boolean)
     _type = Column(String(50))
-    _r_option = Column(Integer, ForeignKey('requireoption.id'))
     __mapper_args__ = {
         'polymorphic_identity': 'option',
         'polymorphic_on': _type
@@ -227,8 +261,8 @@ class _Base(SqlAlchemyBase):
             prop_obj = _PropertyOption(propname)
         return prop_obj
 
-    def _add_require(self, require):
-        self._requires.append(_RequireOption(*require))
+    #def _add_require(self, require):
+    #    self._requires.append(_RequireOption(*require))
 
     def _add_callback(self, key, values):
         self._callback_params.append(_CallbackParam(key, values))
@@ -282,7 +316,6 @@ class StorageOptionDescription(object):
 
     def impl_get_path_by_opt(self, opt):
         try:
-            print opt, type(opt)
             return self._cache_paths[1][self._cache_paths[0].index(opt.id)]
         except ValueError:
             raise AttributeError(_('no option {0} found').format(opt))