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)
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]

View 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_):

View 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):

View File

@ -20,10 +20,12 @@
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'))
)
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 _RequireExpected(SqlAlchemyBase):
__tablename__ = 'expected'
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))