require works well in sqlalchemy storage

This commit is contained in:
Emmanuel Garette 2014-01-27 17:16:05 +01:00
parent d3f42efe85
commit a1dd2cfce7
4 changed files with 167 additions and 109 deletions

View File

@ -311,10 +311,10 @@ def test_append_properties():
cfg = Config(descr) cfg = Config(descr)
setting = cfg.cfgimpl_get_settings() setting = cfg.cfgimpl_get_settings()
option = cfg.cfgimpl_get_description().gc.dummy option = cfg.cfgimpl_get_description().gc.dummy
assert tuple(option.impl_getproperties()) == tuple() assert tuple(option._properties) == tuple()
assert not 'test' in setting[option] assert not 'test' in setting[option]
setting[option].append('test') setting[option].append('test')
assert tuple(option.impl_getproperties()) == tuple() assert tuple(option._properties) == tuple()
assert 'test' in setting[option] assert 'test' in setting[option]

View File

@ -67,11 +67,9 @@ class Base(StorageBase):
raise ValueError(_("invalid name: {0} for option").format(name)) raise ValueError(_("invalid name: {0} for option").format(name))
self._name = name self._name = name
self.impl_set_information('doc', doc) self.impl_set_information('doc', doc)
requires = validate_requires_arg(requires, self._name)
if requires is not None: if requires is not None:
for values in requires.values(): self._calc_properties, self._requires = validate_requires_arg(
for require in values.values(): requires, self._name)
self._add_require(require)
if not multi and default_multi is not None: if not multi and default_multi is not None:
raise ValueError(_("a default_multi is set whereas multi is False" raise ValueError(_("a default_multi is set whereas multi is False"
" in option: {0}").format(name)) " in option: {0}").format(name))
@ -118,8 +116,8 @@ class Base(StorageBase):
if callback_params is not None: if callback_params is not None:
for key, values in callback_params.items(): for key, values in callback_params.items():
self._add_callback(key, values) self._add_callback(key, values)
if requires is not None and properties is not tuple(): if self._calc_properties != frozenset([]) and properties is not tuple():
set_forbidden_properties = set(properties) & set(requires.keys()) set_forbidden_properties = self._calc_properties & set(properties)
if set_forbidden_properties != frozenset(): if set_forbidden_properties != frozenset():
raise ValueError('conflict: properties already set in ' raise ValueError('conflict: properties already set in '
'requirement {0}'.format( 'requirement {0}'.format(
@ -133,8 +131,9 @@ class Base(StorageBase):
self._default = [] self._default = []
else: else:
self._default = default self._default = default
for prop in properties: self._properties = properties
self._properties.append(self._get_property_object(prop)) #for prop in properties:
#self._properties.append(self._get_property_object(prop))
self._warnings_only = warnings_only self._warnings_only = warnings_only
return super(Base, self).__init__() return super(Base, self).__init__()
@ -148,6 +147,29 @@ class BaseOption(Base):
# '_calc_properties', '_impl_informations', # '_calc_properties', '_impl_informations',
# '_state_readonly', '_state_requires', '_stated') # '_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 # serialize object
def _impl_convert_requires(self, descr, load=False): def _impl_convert_requires(self, descr, load=False):
@ -349,9 +371,9 @@ class Option(BaseOption):
# name)) # name))
# object.__setattr__(self, name, value) # object.__setattr__(self, name, value)
def impl_getproperties(self): #def impl_getproperties(self):
for prop in self._properties: # for prop in self._properties:
yield(prop.name) # yield(prop.name)
def impl_getrequires(self): def impl_getrequires(self):
return self._requires return self._requires
@ -1161,10 +1183,10 @@ class OptionDescription(BaseOption, StorageOptionDescription):
# the group_type is useful for filtering OptionDescriptions in a config # the group_type is useful for filtering OptionDescriptions in a config
self._optiondescription_group_type = groups.default self._optiondescription_group_type = groups.default
def impl_getproperties(self): #def impl_getproperties(self):
#FIXME # #FIXME
for prop in self._properties: # for prop in self._properties:
yield(prop.name) # yield(prop.name)
def impl_getrequires(self): def impl_getrequires(self):
#FIXME #FIXME
@ -1223,14 +1245,16 @@ class OptionDescription(BaseOption, StorageOptionDescription):
#for child in self._children: #for child in self._children:
# yield(session.query(child._type).filter_by(id=child.id).first()) # 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 ! #FIXME cache_option !
if _consistencies is None: if _consistencies is None:
init = True init = True
_consistencies = {} _consistencies = {}
cache_option = []
else: else:
init = False init = False
for option in self.impl_getchildren(): for option in self.impl_getchildren():
cache_option.append(option.id)
if not isinstance(option, OptionDescription): if not isinstance(option, OptionDescription):
for consistency in option._consistencies: for consistency in option._consistencies:
func = consistency.func func = consistency.func
@ -1240,15 +1264,15 @@ class OptionDescription(BaseOption, StorageOptionDescription):
[]).append((func, []).append((func,
all_cons_opts)) all_cons_opts))
else: else:
option.impl_build_cache_consistency(_consistencies) option.impl_build_cache_consistency(_consistencies, cache_option)
if init and _consistencies != {}: if init and _consistencies != {}:
self._cache_consistencies = {} self._cache_consistencies = {}
for opt, cons in _consistencies.items(): for opt, cons in _consistencies.items():
#FIXME dans le cache ... #FIXME dans le cache ...
#if opt.id not in cache_option: if opt.id not in cache_option:
# raise ConfigError(_('consistency with option {0} ' raise ConfigError(_('consistency with option {0} '
# 'which is not in Config').format( 'which is not in Config').format(
# opt.impl_getname())) opt.impl_getname()))
self._cache_consistencies[opt] = tuple(cons) self._cache_consistencies[opt] = tuple(cons)
def impl_validate_options(self, cache_option=None): def impl_validate_options(self, cache_option=None):
@ -1509,15 +1533,15 @@ def validate_requires_arg(requires, name):
inverse, transitive, same_action) inverse, transitive, same_action)
else: else:
ret_requires[action][option][1].append(expected) ret_requires[action][option][1].append(expected)
## transform dict to tuple # transform dict to tuple
#ret = [] ret = []
#for opt_requires in ret_requires.values(): for opt_requires in ret_requires.values():
# ret_action = [] ret_action = []
# for require in opt_requires.values(): for require in opt_requires.values():
# ret_action.append((require[0], tuple(require[1]), require[2], ret_action.append((require[0], tuple(require[1]), require[2],
# require[3], require[4], require[5])) require[3], require[4], require[5]))
# ret.append(tuple(ret_action)) ret.append(tuple(ret_action))
return ret_requires return frozenset(config_action.keys()), tuple(ret)
def validate_callback(callback, callback_params, type_): def validate_callback(callback, callback_params, type_):

View File

@ -385,7 +385,7 @@ class Settings(object):
if is_cached: if is_cached:
return props return props
#FIXME #FIXME
props = self._p_.getproperties(path, opt.impl_getproperties()) props = self._p_.getproperties(path, opt._properties)
if is_apply_req: if is_apply_req:
props |= self.apply_requires(opt, path) props |= self.apply_requires(opt, path)
if 'cache' in self: if 'cache' in self:
@ -589,16 +589,16 @@ class Settings(object):
:param path: the option's path in the config :param path: the option's path in the config
:type path: str :type path: str
""" """
if opt.impl_getrequires() is None: if opt._requires is None:
return frozenset() return frozenset()
# filters the callbacks # filters the callbacks
calc_properties = set() calc_properties = set()
context = self._getcontext() context = self._getcontext()
for require in opt.impl_getrequires(): for requires in opt._requires:
expected = tuple(require.get_expected()) for require in requires:
inverse = require.inverse option, expected, action, inverse, \
option = require.option transitive, same_action = require
reqpath = self._get_path_by_opt(option) reqpath = self._get_path_by_opt(option)
if reqpath == path or reqpath.startswith(path + '.'): if reqpath == path or reqpath.startswith(path + '.'):
raise RequirementError(_("malformed requirements " raise RequirementError(_("malformed requirements "
@ -609,24 +609,25 @@ class Settings(object):
value = context._getattr(reqpath, value = context._getattr(reqpath,
force_permissive=True) force_permissive=True)
except PropertiesOptionError as err: except PropertiesOptionError as err:
if not require.transitive: if not transitive:
continue continue
properties = err.proptype properties = err.proptype
if require.same_action and require.action not in properties: if same_action and action not in properties:
raise RequirementError(_("option '{0}' has " raise RequirementError(_("option '{0}' has "
"requirement's property " "requirement's property "
"error: " "error: "
"{1} {2}").format(opt.impl_getname(), "{1} {2}").format(opt._name,
reqpath, reqpath,
properties)) properties))
# transitive action, force expected # transitive action, force expected
value = expected[0] value = expected[0]
inverse = False inverse = False
if not inverse and value in expected or \ if (not inverse and
inverse and value not in expected: value in expected or
calc_properties.add(require.action) inverse and value not in expected):
calc_properties.add(action)
# the calculation cannot be carried out # the calculation cannot be carried out
#break break
return calc_properties return calc_properties
def _get_path_by_opt(self, opt): def _get_path_by_opt(self, opt):

View File

@ -20,10 +20,12 @@
from tiramisu.i18n import _ from tiramisu.i18n import _
from sqlalchemy.ext.declarative import declarative_base, declared_attr 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, \ from sqlalchemy import create_engine, Column, Integer, String, Boolean, \
PickleType, ForeignKey, Table PickleType, ForeignKey, Table
from sqlalchemy.orm import relationship, backref from sqlalchemy.orm import relationship, backref
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.collections import attribute_mapped_collection
#FIXME #FIXME
@ -39,68 +41,91 @@ SqlAlchemyBase = declarative_base()
#_Base : object dans la base de donnée #_Base : object dans la base de donnée
# => _RequireOption => il y a une liste d'espect dans _RequireExpected
# => _PropertyOption => liste des propriétés # => _PropertyOption => liste des propriétés
# => _Information => dictionnaire avec clef valeur
# => _CallbackParam avec des Options # => _CallbackParam avec des Options
require_table = Table('require', SqlAlchemyBase.metadata, def load_requires(collection_type, proxy):
Column('left_id', Integer, ForeignKey('requireoption.id')), def getter(obj):
Column('right_id', Integer, ForeignKey('baseoption.id')) 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 _RequireExpected(SqlAlchemyBase): class _Require(SqlAlchemyBase):
__tablename__ = 'expected' __tablename__ = "require"
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
expected = Column(PickleType) requires_id = Column(Integer, ForeignKey("baseoption.id"), nullable=False)
require = Column(Integer, ForeignKey('requireoption.id')) requires = relationship('_RequireOption')
def __init__(self, expected): def __init__(self, requires):
self.expected = expected for require in requires:
self.requires.append(_RequireOption(require))
class _RequireOption(SqlAlchemyBase): class _RequireOption(SqlAlchemyBase):
__tablename__ = 'requireoption' __tablename__ = 'requireoption'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
option = relationship('_Base', lazy='joined', cascade="all, delete-orphan") require_id = Column(Integer, ForeignKey("require.id"), nullable=False)
#option = relationship('_Base') option = Column(Integer, nullable=False)
expected = relationship("_RequireExpected") _expected = relationship("_RequireExpected", collection_class=list,
cascade="all, delete-orphan")
expected = association_proxy("_expected", "expected")
#expected = Column(String)
action = Column(String, nullable=False) action = Column(String, nullable=False)
inverse = Column(Boolean, default=False) inverse = Column(Boolean, default=False)
transitive = Column(Boolean, default=True) transitive = Column(Boolean, default=True)
same_action = Column(Boolean, default=True) same_action = Column(Boolean, default=True)
def __init__(self, option, expected, action, inverse, transitive, def __init__(self, values):
same_action): option, expected, action, inverse, transitive, same_action = values
#self.r_opt = option.id self.option = option.id
self.option = option self.expected = expected
for expect in expected:
self.expected.append(_RequireExpected(expect))
self.action = action self.action = action
self.inverse = inverse self.inverse = inverse
self.transitive = transitive self.transitive = transitive
self.same_action = same_action self.same_action = same_action
def get_expected(self):
for expected in self.expected:
yield(expected.expected)
#def get_option(self, config): class _RequireExpected(SqlAlchemyBase):
# return config.cfgimpl_get_description().impl_get_opt_by_id(self.r_opt) __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 # properties
property_table = Table('property', SqlAlchemyBase.metadata,
Column('left_id', Integer, ForeignKey('propertyoption.name')),
Column('right_id', Integer, ForeignKey('baseoption.id'))
)
class _PropertyOption(SqlAlchemyBase): class _PropertyOption(SqlAlchemyBase):
__tablename__ = 'propertyoption' __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): def __init__(self, name):
self.name = name self.name = name
@ -112,7 +137,7 @@ class _PropertyOption(SqlAlchemyBase):
class _Information(SqlAlchemyBase): class _Information(SqlAlchemyBase):
__tablename__ = 'information' __tablename__ = 'information'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
option = Column(Integer, ForeignKey('baseoption.id')) option = Column(Integer, ForeignKey('baseoption.id'), nullable=False)
key = Column(String) key = Column(String)
value = Column(PickleType) value = Column(PickleType)
@ -184,11 +209,15 @@ class _Base(SqlAlchemyBase):
__tablename__ = 'baseoption' __tablename__ = 'baseoption'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
_name = Column(String) _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 = Column(PickleType)
_default_multi = Column(PickleType) _default_multi = Column(PickleType)
_requires = relationship('_RequireOption', secondary=require_table, _reqs = relationship("_Require", collection_class=list)
backref=backref('self_option', enable_typechecks=False)) _requires = association_proxy("_reqs", "requires", getset_factory=load_requires)
_multi = Column(Boolean) _multi = Column(Boolean)
_multitype = Column(String) _multitype = Column(String)
_callback = Column(PickleType) _callback = Column(PickleType)
@ -197,8 +226,14 @@ class _Base(SqlAlchemyBase):
_validator_params = relationship('_CallbackParam') _validator_params = relationship('_CallbackParam')
_parent = Column(Integer, ForeignKey('baseoption.id')) _parent = Column(Integer, ForeignKey('baseoption.id'))
_children = relationship('BaseOption', enable_typechecks=False) _children = relationship('BaseOption', enable_typechecks=False)
_properties = relationship('_PropertyOption', secondary=property_table, #FIXME pas 2 fois la meme properties dans la base ...
backref=backref('options', enable_typechecks=False)) #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) _warnings_only = Column(Boolean)
_readonly = Column(Boolean, default=False) _readonly = Column(Boolean, default=False)
_consistencies = relationship('_Consistency', secondary=consistency_table, _consistencies = relationship('_Consistency', secondary=consistency_table,
@ -206,7 +241,6 @@ class _Base(SqlAlchemyBase):
_choice_values = Column(PickleType) _choice_values = Column(PickleType)
_choice_open_values = Column(Boolean) _choice_open_values = Column(Boolean)
_type = Column(String(50)) _type = Column(String(50))
_r_option = Column(Integer, ForeignKey('requireoption.id'))
__mapper_args__ = { __mapper_args__ = {
'polymorphic_identity': 'option', 'polymorphic_identity': 'option',
'polymorphic_on': _type 'polymorphic_on': _type
@ -227,8 +261,8 @@ class _Base(SqlAlchemyBase):
prop_obj = _PropertyOption(propname) prop_obj = _PropertyOption(propname)
return prop_obj return prop_obj
def _add_require(self, require): #def _add_require(self, require):
self._requires.append(_RequireOption(*require)) # self._requires.append(_RequireOption(*require))
def _add_callback(self, key, values): def _add_callback(self, key, values):
self._callback_params.append(_CallbackParam(key, values)) self._callback_params.append(_CallbackParam(key, values))
@ -282,7 +316,6 @@ class StorageOptionDescription(object):
def impl_get_path_by_opt(self, opt): def impl_get_path_by_opt(self, opt):
try: try:
print opt, type(opt)
return self._cache_paths[1][self._cache_paths[0].index(opt.id)] return self._cache_paths[1][self._cache_paths[0].index(opt.id)]
except ValueError: except ValueError:
raise AttributeError(_('no option {0} found').format(opt)) raise AttributeError(_('no option {0} found').format(opt))