better support for sqlalchemy storage

This commit is contained in:
Emmanuel Garette 2014-07-06 15:31:57 +02:00
parent 3cc2d9ca3d
commit 71f8926fca
16 changed files with 369 additions and 241 deletions

View file

@ -37,6 +37,14 @@ def return_list(val=None):
return ['val1', 'val2']
def return_same_list():
return ['val1', 'val1']
def return_wrong_list():
return ['---', ' ']
def test_build_dyndescription():
st = StrOption('st', '')
dod = DynOptionDescription('dod', '', [st], callback=return_list)
@ -193,18 +201,18 @@ def test_prop_dyndescription():
stval2 = cfg.unwrap_from_path('od.dodval2.stval2')
dodval1 = cfg.unwrap_from_path('od.dodval1')
dodval2 = cfg.unwrap_from_path('od.dodval2')
assert str(cfg.cfgimpl_get_settings()[stval1]) == str(['test'])
assert str(cfg.cfgimpl_get_settings()[stval2]) == str(['test'])
assert str(cfg.cfgimpl_get_settings()[stval1]) in [str(['test']), str([u'test'])]
assert str(cfg.cfgimpl_get_settings()[stval2]) in [str(['test']), str([u'test'])]
cfg.cfgimpl_get_settings()[stval2].append('test2')
assert str(cfg.cfgimpl_get_settings()[stval1]) == str(['test'])
assert str(cfg.cfgimpl_get_settings()[stval2]) == str(['test', 'test2'])
assert str(cfg.cfgimpl_get_settings()[stval1]) in [str(['test']), str([u'test'])]
assert str(cfg.cfgimpl_get_settings()[stval2]) in [str(['test', 'test2']), str([u'test', 'test2'])]
cfg.cfgimpl_get_settings()[stval1].remove('test')
assert str(cfg.cfgimpl_get_settings()[stval1]) == str([])
#
assert str(cfg.cfgimpl_get_settings()[dodval1]) == str([])
assert str(cfg.cfgimpl_get_settings()[dodval2]) == str([])
cfg.cfgimpl_get_settings()[dodval1].append('test1')
assert str(cfg.cfgimpl_get_settings()[dodval1]) == str(['test1'])
assert str(cfg.cfgimpl_get_settings()[dodval1]) in [str(['test1']), str([u'test1'])]
assert str(cfg.cfgimpl_get_settings()[dodval2]) == str([])
cfg.cfgimpl_get_settings()[dodval1].remove('test1')
assert str(cfg.cfgimpl_get_settings()[dodval1]) == str([])
@ -402,11 +410,11 @@ def test_prop_dyndescription_context():
cfg = Config(od2)
stval1 = cfg.unwrap_from_path('od.dodval1.stval1')
stval2 = cfg.unwrap_from_path('od.dodval2.stval2')
assert str(cfg.cfgimpl_get_settings()[stval1]) == str(['test'])
assert str(cfg.cfgimpl_get_settings()[stval2]) == str(['test'])
assert str(cfg.cfgimpl_get_settings()[stval1]) in [str(['test']), str([u'test'])]
assert str(cfg.cfgimpl_get_settings()[stval2]) in [str(['test']), str([u'test'])]
cfg.cfgimpl_get_settings()[stval2].append('test2')
assert str(cfg.cfgimpl_get_settings()[stval1]) == str(['test'])
assert str(cfg.cfgimpl_get_settings()[stval2]) == str(['test', 'test2'])
assert str(cfg.cfgimpl_get_settings()[stval1]) in [str(['test']), str([u'test'])]
assert str(cfg.cfgimpl_get_settings()[stval2]) in [str(['test', 'test2']), str([u'test', 'test2'])]
cfg.cfgimpl_get_settings()[stval1].remove('test')
assert str(cfg.cfgimpl_get_settings()[stval1]) == str([])
@ -1293,13 +1301,11 @@ def test_invalid_symlink_dyndescription():
def test_nocallback_dyndescription():
st = StrOption('st', '')
st2 = StrOption('st2', st)
st2 = StrOption('st2', '')
raises(ConfigError, "DynOptionDescription('dod', '', [st, st2])")
def test_invalid_samevalue_dyndescription():
def return_same_list():
return ['val1', 'val1']
st = StrOption('st', '')
dod = DynOptionDescription('dod', '', [st], callback=return_same_list)
od = OptionDescription('od', '', [dod])
@ -1308,10 +1314,8 @@ def test_invalid_samevalue_dyndescription():
def test_invalid_name_dyndescription():
def return_same_list():
return ['---', ' ']
st = StrOption('st', '')
dod = DynOptionDescription('dod', '', [st], callback=return_same_list)
dod = DynOptionDescription('dod', '', [st], callback=return_wrong_list)
od = OptionDescription('od', '', [dod])
cfg = Config(od)
raises(ValueError, "print cfg")

View file

@ -382,4 +382,4 @@ def test_properties_cached():
setting = c.cfgimpl_get_settings()
option = c.cfgimpl_get_description().sub.b1
c._setattr('sub.b1', True, force_permissive=True)
assert str(setting[b1]) == "['test']"
assert str(setting[b1]) in ["['test']", "[u'test']"]

View file

@ -149,7 +149,8 @@ def carry_out_calculation(option, config, callback, callback_params,
if isinstance(callbk, tuple):
if config is undefined:
raise ContextError() # pragma: optional cover
if len(callbk) == 1: # pragma: optional cover
if callbk[0] is None: # pragma: optional cover
#Not an option, set full context
tcparams.setdefault(key, []).append((config, False))
else:
# callbk is something link (opt, True|False)

View file

@ -33,11 +33,7 @@ from tiramisu.storage import get_storages_option
StorageBase = get_storages_option('base')
class SubMulti(object):
pass
submulti = SubMulti()
submulti = 2
allowed_character = '[a-z\d\-_]'
@ -115,7 +111,10 @@ class Base(StorageBase):
if callback is not None:
validate_callback(callback, callback_params, 'callback')
self._callback = callback
self._callback_params = callback_params
if callback_params is None:
self._callback_params = {}
else:
self._callback_params = callback_params
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None,
@ -143,8 +142,13 @@ class Base(StorageBase):
raise ValueError(_("invalid default_multi value {0} "
"for option {1}: {2}").format(
str(default_multi), name, err))
self._multi = multi
if self._multi is not False:
if multi is True:
self._multi = 0
elif multi is False:
self._multi = 1
elif multi is submulti:
self._multi = submulti
if self._multi != 1:
if default is None:
default = []
self._default_multi = default_multi
@ -260,10 +264,10 @@ class BaseOption(Base):
return
if not load and self._callback is None:
self._state_callback = None
self._state_callback_params = None
self._state_callback_params = {}
elif load and self._state_callback is None:
self._callback = None
self._callback_params = None
self._callback_params = {}
del(self._state_callback)
del(self._state_callback_params)
else:
@ -273,7 +277,7 @@ class BaseOption(Base):
else:
callback = self._callback
callback_params = self._callback_params
self._state_callback_params = None
self._state_callback_params = {}
if callback_params is not None:
cllbck_prms = {}
for key, values in callback_params.items():
@ -429,6 +433,19 @@ class BaseOption(Base):
def impl_get_callback(self):
return self._callback, self._callback_params
def impl_has_callback(self):
"to know if a callback has been defined or not"
return self._callback is not None
def _is_subdyn(self):
try:
return self._subdyn is not None
except AttributeError:
return False
def impl_getproperties(self):
return self._properties
class OnlyOption(BaseOption):
__slots__ = tuple()
@ -713,18 +730,11 @@ class Option(OnlyOption):
"accesses the Option's doc"
return self.impl_get_information('doc')
def impl_has_callback(self):
"to know if a callback has been defined or not"
if self._callback is None:
return False
else:
return True
#def impl_getkey(self, value):
# return value
def impl_is_multi(self):
return self._multi is True or self._multi is submulti
return self._multi == 0 or self._multi is submulti
def impl_is_submulti(self):
return self._multi is submulti
@ -746,7 +756,7 @@ class Option(OnlyOption):
self._name))
warnings_only = params.get('warnings_only', False)
if self._is_subdyn():
dynod = self._subdyn
dynod = self._impl_getsubdyn()
else:
dynod = None
for opt in other_opts:
@ -756,10 +766,10 @@ class Option(OnlyOption):
if dynod is None:
raise ConfigError(_('almost one option in consistency is '
'in a dynoptiondescription but not all'))
if dynod != opt._subdyn:
if dynod != opt._impl_getsubdyn():
raise ConfigError(_('option in consistency must be in same'
' dynoptiondescription'))
dynod = opt._subdyn
dynod = opt._impl_getsubdyn()
elif dynod is not None:
raise ConfigError(_('almost one option in consistency is in a '
'dynoptiondescription but not all'))
@ -847,10 +857,10 @@ class Option(OnlyOption):
default_multi = self._default_multi
except AttributeError:
default_multi = None
if callback is not None and ((not self._multi and
if callback is not None and ((self._multi == 1 and
(self._default is not None or
default_multi is not None))
or (self._multi and
or (self._multi != 1 and
(self._default != [] or
default_multi is not None))
): # pragma: optional cover
@ -983,6 +993,23 @@ class SymLinkOption(OnlyOption):
def impl_get_information(self, key, default=undefined):
return self._opt.impl_get_information(key, default)
#FIXME utile tout ca ? c'est un peu de la duplication ...
def impl_getproperties(self):
return self._opt._properties
def impl_get_callback(self):
return self._opt._callback, self._opt._callback_params
def impl_has_callback(self):
"to know if a callback has been defined or not"
return self._opt._callback is not None
def _is_subdyn(self):
try:
return self._opt._subdyn is not None
except AttributeError:
return False
class DynSymLinkOption(SymLinkOption):
__slots__ = ('_dyn',)

View file

@ -55,7 +55,7 @@ class MasterSlaves(object):
).format(name))
if validate:
callback, callback_params = self.master.impl_get_callback()
if callback is not None and callback_params is not None: # pragma: optional cover
if callback is not None and callback_params != {}: # pragma: optional cover
for key, callbacks in callback_params.items():
for callbk in callbacks:
if isinstance(callbk, tuple):

View file

@ -48,11 +48,14 @@ class ChoiceOption(Option):
"""
if isinstance(values, FunctionType):
validate_callback(values, values_params, 'values')
elif not isinstance(values, tuple): # pragma: optional cover
raise TypeError(_('values must be a tuple or a function for {0}'
).format(name))
self._extra = {'_choice_values': values,
'_choice_values_params': values_params}
else:
if values_params is not None:
raise ValueError(_('values is not a function, so values_params must be None'))
if not isinstance(values, tuple): # pragma: optional cover
raise TypeError(_('values must be a tuple or a function for {0}'
).format(name))
self._choice_values = values
self._choice_values_params = values_params
super(ChoiceOption, self).__init__(name, doc, default=default,
default_multi=default_multi,
callback=callback,
@ -66,9 +69,9 @@ class ChoiceOption(Option):
def impl_get_values(self, context):
#FIXME cache? but in context...
values = self._extra['_choice_values']
values = self._choice_values
if isinstance(values, FunctionType):
values_params = self._extra['_choice_values_params']
values_params = self._choice_values_params
if values_params is None:
values_params = {}
values = carry_out_calculation(self, config=context,

View file

@ -74,7 +74,6 @@ class OptionDescription(BaseOption, StorageOptionDescription):
'dynoptiondescription'))
old = child
self._add_children(child_names, children)
self._cache_paths = None
self._cache_consistencies = None
# the group_type is useful for filtering OptionDescriptions in a config
self._group_type = groups.default
@ -167,9 +166,6 @@ class OptionDescription(BaseOption, StorageOptionDescription):
raise ValueError(_('group_type: {0}'
' not allowed').format(group_type))
def impl_get_group_type(self):
return self._group_type
def _valid_consistency(self, option, value, context, index, submulti_idx):
if self._cache_consistencies is None:
return True
@ -239,7 +235,6 @@ class OptionDescription(BaseOption, StorageOptionDescription):
:param descr: parent :class:`tiramisu.option.OptionDescription`
"""
if descr is None:
self._cache_paths = None
self._cache_consistencies = None
self.impl_build_cache_option()
descr = self
@ -261,8 +256,6 @@ class OptionDescription(BaseOption, StorageOptionDescription):
def _impl_get_suffixes(self, context):
callback, callback_params = self.impl_get_callback()
if callback_params is None:
callback_params = {}
values = carry_out_calculation(self, config=context,
callback=callback,
callback_params=callback_params)
@ -275,10 +268,9 @@ class OptionDescription(BaseOption, StorageOptionDescription):
def _impl_search_dynchild(self, name=undefined, context=undefined):
ret = []
for child in self._impl_st_getchildren():
for child in self._impl_st_getchildren(context, only_dyn=True):
cname = child.impl_getname()
if isinstance(child, DynOptionDescription) and \
(name is undefined or name.startswith(cname)):
if name is undefined or name.startswith(cname):
path = cname
for value in child._impl_get_suffixes(context):
if name is undefined:
@ -296,7 +288,7 @@ class OptionDescription(BaseOption, StorageOptionDescription):
return child._impl_to_dyn(name, path)
def _impl_getchildren(self, dyn=True, context=undefined):
for child in self._impl_st_getchildren():
for child in self._impl_st_getchildren(context):
cname = child._name
if dyn and child.impl_is_dynoptiondescription():
path = cname
@ -310,24 +302,30 @@ class OptionDescription(BaseOption, StorageOptionDescription):
def impl_getchildren(self):
return list(self._impl_getchildren())
def __getattr__(self, name, context=undefined):
if name.startswith('_'): # or name.startswith('impl_'):
return object.__getattribute__(self, name)
return self._getattr(name, context=context)
class DynOptionDescription(OptionDescription):
def __init__(self, name, doc, children, requires=None, properties=None,
callback=None, callback_params=None):
super(DynOptionDescription, self).__init__(name, doc, children,
requires, properties)
for child in children:
if isinstance(child, OptionDescription):
if child.impl_get_group_type() != groups.master:
raise ConfigError(_('cannot set optiondescription in an '
'dynoptiondescription'))
for chld in child._impl_getchildren():
chld._subdyn = self
chld._impl_setsubdyn(self)
if isinstance(child, SymLinkOption):
raise ConfigError(_('cannot set symlinkoption in an '
'dynoptiondescription'))
child._subdyn = self
super(DynOptionDescription, self).__init__(name, doc, children,
requires, properties)
child._impl_setsubdyn(self)
self.impl_set_callback(callback, callback_params)
self.commit()
def _validate_callback(self, callback, callback_params):
if callback is None:
@ -346,7 +344,7 @@ class SynDynOptionDescription(object):
def __getattr__(self, name, context=undefined):
if name in dir(self._opt):
return getattr(self._opt, name)
return self._opt._getattr(name, self._name, self._suffix, context)
return self._opt._getattr(name, suffix=self._suffix, context=context)
def impl_getname(self):
return self._name

View file

@ -100,6 +100,9 @@ rw_append = set(['frozen', 'disabled', 'validator', 'hidden'])
rw_remove = set(['permissive', 'everything_frozen', 'mandatory'])
forbidden_set_properties = set(['force_store_value'])
log = getLogger('tiramisu')
#FIXME
#import logging
@ -253,7 +256,7 @@ class Property(object):
'this property is calculated').format(
propname, self._opt.impl_getname()))
self._properties.add(propname)
self._setting._setproperties(self._properties, self._opt, self._path)
self._setting._setproperties(self._properties, self._path)
def remove(self, propname):
"""Removes a property named propname
@ -263,8 +266,7 @@ class Property(object):
"""
if propname in self._properties:
self._properties.remove(propname)
self._setting._setproperties(self._properties, self._opt,
self._path)
self._setting._setproperties(self._properties, self._path)
def extend(self, propnames):
"""Extends properties to the existing properties
@ -347,7 +349,7 @@ class Settings(object):
else:
if opt is not None and _path is None:
_path = opt.impl_getpath(self._getcontext())
self._p_.reset_properties(_path)
self._p_.delproperties(_path)
self._getcontext().cfgimpl_reset_cache()
def _getproperties(self, opt=None, path=None, _is_apply_req=True):
@ -367,7 +369,7 @@ class Settings(object):
is_cached, props = self._p_.getcache(path, ntime)
if is_cached:
return copy(props)
props = self._p_.getproperties(path, opt._properties)
props = self._p_.getproperties(path, opt.impl_getproperties())
if _is_apply_req:
props = copy(props)
props |= self.apply_requires(opt, path)
@ -383,33 +385,28 @@ class Settings(object):
"puts property propname in the Config's properties attribute"
props = self._p_.getproperties(None, default_properties)
props.add(propname)
self._setproperties(props, None, None)
self._setproperties(props, None)
def remove(self, propname):
"deletes property propname in the Config's properties attribute"
props = self._p_.getproperties(None, default_properties)
if propname in props:
props.remove(propname)
self._setproperties(props, None, None)
self._setproperties(props, None)
def extend(self, propnames):
for propname in propnames:
self.append(propname)
def _setproperties(self, properties, opt, path):
"""save properties for specified opt
def _setproperties(self, properties, path):
"""save properties for specified path
(never save properties if same has option properties)
"""
if opt is None:
self._p_.setproperties(None, properties)
else:
#if opt._calc_properties is not None:
# properties -= opt._calc_properties
#if set(opt._properties) == properties:
# self._p_.reset_properties(path)
#else:
# self._p_.setproperties(path, properties)
self._p_.setproperties(path, properties)
forbidden_properties = forbidden_set_properties & properties
if forbidden_properties:
raise ConfigError(_('cannot add those properties: {0}').format(
' '.join(forbidden_properties)))
self._p_.setproperties(path, properties)
self._getcontext().cfgimpl_reset_cache()
#____________________________________________________________
@ -622,15 +619,6 @@ class Settings(object):
def get_modified_permissives(self):
return self._p_.get_modified_permissives()
def get_with_property(self, propname):
opts, paths = self._getcontext().cfgimpl_get_description(
)._cache_paths
for index in range(0, len(paths)):
opt = opts[index]
path = paths[index]
if propname in self._getproperties(opt, path, False):
yield (opt, path)
def __getstate__(self):
return {'_p_': self._p_, '_owner': str(self._owner)}

View file

@ -117,15 +117,20 @@ def get_storages(context, session_id, persistent):
session_id = gen_id(context)
imp = storage_type.get()
storage = imp.Storage(session_id, persistent)
return imp.Settings(storage), imp.Values(storage)
try:
return imp.Settings(storage), imp.Values(storage)
except:
import traceback
traceback.print_exc()
raise Exception('rah')
def get_storages_option(type_):
imp = storage_option_type.get()
if type_ == 'base':
return imp.Base
return imp.StorageBase
else:
return imp.OptionDescription
return imp.StorageOptionDescription
def list_sessions(type_): # pragma: optional cover

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2013-2014 Team tiramisu (see AUTHORS for all contributors)
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
@ -25,7 +25,7 @@ use it. But if something goes wrong, you will lost your modifications.
from .value import Values
from .setting import Settings
from .storage import setting, Storage, list_sessions, delete_session
from .option import Base, OptionDescription
from .option import StorageBase, StorageOptionDescription
__all__ = (setting, Values, Settings, Storage, list_sessions, delete_session,
Base, OptionDescription)
StorageBase, StorageOptionDescription)

View file

@ -18,14 +18,14 @@
#
# ____________________________________________________________
from tiramisu.i18n import _
from tiramisu.setting import groups, undefined
from tiramisu.setting import undefined
from tiramisu.error import ConfigError
#____________________________________________________________
#
# Base
class Base(object):
class StorageBase(object):
__slots__ = ('_name', '_requires', '_properties', '_readonly',
'_calc_properties', '_informations',
'_state_readonly', '_state_requires', '_stated',
@ -34,13 +34,14 @@ class Base(object):
'_state_callback_params', '_callback_params', '_multitype',
'_consistencies', '_warnings_only', '_master_slaves',
'_state_consistencies', '_extra', '_subdyn', '__weakref__',
'_state_master_slaves')
'_state_master_slaves', '_choice_values',
'_choice_values_params')
def __init__(self):
try:
self._subdyn
except AttributeError:
self._subdyn = False
self._subdyn = None
try:
self._consistencies
except AttributeError:
@ -52,7 +53,7 @@ class Base(object):
try:
self._callback_params
except AttributeError:
self._callback_params = None
self._callback_params = {}
try:
self._validator
except AttributeError:
@ -71,19 +72,22 @@ class Base(object):
def _get_id(self):
return id(self)
def _is_subdyn(self):
try:
return self._subdyn is not False
except AttributeError:
return False
def _impl_getsubdyn(self):
return self._subdyn
def _impl_setsubdyn(self, subdyn):
self._subdyn = subdyn
def commit(self):
pass
class OptionDescription(Base):
class StorageOptionDescription(StorageBase):
__slots__ = ('_children', '_cache_paths', '_cache_consistencies',
'_group_type', '_is_build_cache', '_state_group_type')
def __init__(self):
pass
self._cache_paths = None
def _add_children(self, child_names, children):
self._children = (tuple(child_names), tuple(children))
@ -104,10 +108,14 @@ class OptionDescription(Base):
raise AttributeError(_('no option {0} found').format(opt))
def impl_get_group_type(self): # pragma: optional cover
return getattr(groups, self._group_type)
return self._group_type
def impl_build_cache_option(self, _currpath=None, cache_path=None,
cache_option=None):
try:
self._cache_paths
except AttributeError:
self._cache_paths = None
if _currpath is None and self._cache_paths is not None: # pragma: optional cover
# cache already set
return
@ -145,8 +153,7 @@ class OptionDescription(Base):
found = True
break
if not found:
#FIXME
raise ConfigError(_('hu?'))
raise ConfigError(_('cannot find dynpath'))
subpath = subpath + suffix
for slength in xrange(length, len(spath)):
subpath = subpath + '.' + spath[slength] + suffix
@ -226,21 +233,17 @@ class OptionDescription(Base):
return find_results[0]
return find_results
def _impl_st_getchildren(self):
return self._children[1]
def _impl_st_getchildren(self, context, only_dyn=False):
for child in self._children[1]:
if only_dyn is False or child.impl_is_dynoptiondescription():
yield(child)
def __getattr__(self, name, context=undefined):
if name == '_name':
return object.__getattribute__(self, name)
return self._getattr(name, context=context)
def _getattr(self, name, dyn_od=undefined, suffix=undefined,
context=undefined, dyn=True):
def _getattr(self, name, suffix=undefined, context=undefined, dyn=True):
error = False
if suffix is not undefined:
try:
if undefined in [dyn_od, suffix, context]: # pragma: optional cover
raise ConfigError(_("dyn_od, suffix and context needed if "
if undefined in [suffix, context]: # pragma: optional cover
raise ConfigError(_("suffix and context needed if "
"it's a dyn option"))
if name.endswith(suffix):
oname = name[:-len(suffix)]
@ -270,3 +273,13 @@ class OptionDescription(Base):
raise AttributeError(_('unknown Option {0} '
'in OptionDescription {1}'
'').format(name, self._name))
def _get_force_store_value(self):
#FIXME faire des tests (notamment pas ajouter à un config)
#FIXME devrait faire un cache !
opts, paths = self._cache_paths
for index in range(0, len(paths)):
opt = opts[index]
path = paths[index]
if 'force_store_value' in opt._properties:
yield (opt, path)

View file

@ -42,13 +42,12 @@ class Settings(Cache):
def reset_all_properties(self):
self._properties.clear()
def reset_properties(self, path):
def delproperties(self, path):
try:
del(self._properties[path])
except KeyError:
pass
# permissive
def setpermissive(self, path, permissive):
self._permissives[path] = frozenset(permissive)

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"default plugin for value: set it in a simple dictionary"
# Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2013-2014 Team tiramisu (see AUTHORS for all contributors)
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the

View file

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014 Team tiramisu (see AUTHORS for all contributors)
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________
"""Default plugin for storage. All informations are store in a simple
dictionary in memory.
You cannot have persistente informations with this kind of storage.
The advantage of this solution is that you can easily create a Config and
use it. But if something goes wrong, you will lost your modifications.
"""
from .value import Values
from .setting import Settings
from .storage import Storage, list_sessions, delete_session, setting
from .option import StorageBase, StorageOptionDescription
from .util import load
load()
__all__ = (setting, Values, Settings, Storage, list_sessions, delete_session,
StorageBase, StorageOptionDescription)
# Base, OptionDescription)

View file

@ -18,28 +18,20 @@
#
# ____________________________________________________________
from tiramisu.i18n import _
from tiramisu.setting import groups
from tiramisu.setting import groups, undefined
from tiramisu.error import ConfigError
from .util import SqlAlchemyBase
import util
from sqlalchemy import not_, or_
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy import create_engine, Column, Integer, String, Boolean, \
PickleType, ForeignKey, Table
from sqlalchemy import Column, Integer, String, Boolean, PickleType, \
ForeignKey, Table
from sqlalchemy.orm import relationship, backref
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.collections import attribute_mapped_collection
#FIXME
engine = create_engine('sqlite:///:memory:')
SqlAlchemyBase = declarative_base()
#FIXME a voir:
# # Organization.members will be a Query object - no loading
# # of the entire collection occurs unless requested
# lazy="dynamic",
#____________________________________________________________
#
# require
from itertools import chain
def load_requires(collection_type, proxy):
@ -49,7 +41,7 @@ def load_requires(collection_type, proxy):
ret = []
requires = getattr(obj, proxy.value_attr)
for require in requires:
option = session.query(_Base).filter_by(id=require.option).first()
option = util.session.query(_Base).filter_by(id=require.option).first()
ret.append(tuple([option, require.expected, require.action, require.inverse, require.transitive, require.same_action]))
return tuple(ret)
@ -158,7 +150,7 @@ def load_callback_parm(collection_type, proxy):
if require.value is not None:
ret.append(require.value)
else:
option = session.query(_Base).filter_by(id=require.option).first()
option = util.session.query(_Base).filter_by(id=require.option).first()
ret.append((option, require.force_permissive))
return tuple(ret)
@ -175,10 +167,10 @@ class _CallbackParamOption(SqlAlchemyBase):
force_permissive = Column(Boolean)
value = Column(PickleType)
def __init__(self, option=None, force_permissive=None, value=None):
if value is not None:
def __init__(self, option=undefined, force_permissive=undefined, value=undefined):
if value is not undefined:
self.value = value
else:
elif option is not undefined:
self.option = option.id
self.force_permissive = force_permissive
@ -194,8 +186,11 @@ class _CallbackParam(SqlAlchemyBase):
self.key = key
for param in params:
if isinstance(param, tuple):
self.params.append(_CallbackParamOption(option=param[0],
force_permissive=param[1]))
if param == (None,):
self.params.append(_CallbackParamOption())
else:
self.params.append(_CallbackParamOption(option=param[0],
force_permissive=param[1]))
else:
self.params.append(_CallbackParamOption(value=param))
@ -215,7 +210,7 @@ class _Consistency(SqlAlchemyBase):
func = Column(PickleType)
params = Column(PickleType)
def __init__(self, func, all_cons_opts):
def __init__(self, func, all_cons_opts, params):
self.func = func
for option in all_cons_opts:
option._consistencies.append(self)
@ -249,9 +244,16 @@ class _Base(SqlAlchemyBase):
_informations = association_proxy("_infos", "value")
_default = Column(PickleType)
_default_multi = Column(PickleType)
_subdyn = Column(Integer)
_choice_values = Column(PickleType)
_cho_params = relationship('_CallbackParam',
collection_class=
attribute_mapped_collection('key'))
_choice_params = association_proxy("_cho_params", "params",
getset_factory=load_callback_parm)
_reqs = relationship("_Require", collection_class=list)
_requires = association_proxy("_reqs", "requires", getset_factory=load_requires)
_multi = Column(Boolean)
_multi = Column(Integer)
_multitype = Column(String)
######
_callback = Column(PickleType)
@ -264,7 +266,7 @@ class _Base(SqlAlchemyBase):
_val_params = relationship('_CallbackParam',
collection_class=
attribute_mapped_collection('key'))
_validator_params = association_proxy("_call_params", "params",
_validator_params = association_proxy("_val_params", "params",
getset_factory=load_callback_parm)
######
#FIXME pas 2 fois la meme properties dans la base ...
@ -290,11 +292,11 @@ class _Base(SqlAlchemyBase):
_is_build_cache = Column(Boolean, default=False)
def __init__(self):
util.session.add(self)
self.commit()
def commit(self):
session.add(self)
session.commit()
util.session.commit()
def _add_consistency(self, func, all_cons_opts, params):
_Consistency(func, all_cons_opts, params)
@ -306,52 +308,35 @@ class _Base(SqlAlchemyBase):
def _get_id(self):
return self.id
# ____________________________________________________________
# information
#def impl_set_information(self, key, value):
# """updates the information's attribute
# (which is a dictionary)
def _impl_getsubdyn(self):
return self._subdyn
# :param key: information's key (ex: "help", "doc"
# :param value: information's value (ex: "the help string")
# """
# info = session.query(_Information).filter_by(option=self.id, key=key).first()
# #FIXME pas append ! remplacer !
# if info is None:
# self._informations.append(_Information(key, value))
# else:
# info.value = value
#def impl_get_information(self, key, default=None):
# """retrieves one information's item
# :param key: the item string (ex: "help")
# """
# info = session.query(_Information).filter_by(option=self.id, key=key).first()
# if info is not None:
# return info.value
# return self._informations[key]
# elif default is not None:
# return default
# else:
# raise ValueError(_("information's item not found: {0}").format(
# key))
def _impl_setsubdyn(self, subdyn):
self._subdyn = subdyn.id
class Cache(SqlAlchemyBase):
__tablename__ = 'cache'
id = Column(Integer, primary_key=True)
#FIXME indexer ... les 3
path = Column(String, nullable=False)
descr = Column(Integer, nullable=False)
option = Column(Integer, nullable=False)
opt_type = Column(String, nullable=False)
path = Column(String, nullable=False, index=True)
descr = Column(Integer, nullable=False, index=True)
parent = Column(Integer, nullable=False, index=True)
option = Column(Integer, nullable=False, index=True)
opt_type = Column(String, nullable=False, index=True)
is_subdyn = Column(Boolean, nullable=False, index=True)
subdyn_path = Column(String)
def __init__(self, descr, option, path):
def __init__(self, descr, parent, option, path, subdyn_path):
self.descr = descr.id
self.parent = parent.id
self.option = option.id
self.path = path
self.opt_type = option.__class__.__name__
#is_subdyn = option._is_subdyn()
is_subdyn = option.impl_is_dynoptiondescription()
self.is_subdyn = is_subdyn
if is_subdyn:
self.subdyn_path = subdyn_path
class StorageOptionDescription(object):
@ -359,14 +344,14 @@ class StorageOptionDescription(object):
return self._is_build_cache
def impl_get_opt_by_path(self, path):
ret = session.query(Cache).filter_by(descr=self.id, path=path).first()
ret = util.session.query(Cache).filter_by(descr=self.id, path=path).first()
if ret is None:
raise AttributeError(_('no option for path {0}').format(path))
return session.query(_Base).filter_by(id=ret.option).first()
return util.session.query(_Base).filter_by(id=ret.option).first()
def impl_get_path_by_opt(self, opt):
ret = session.query(Cache).filter_by(descr=self.id,
option=opt.id).first()
ret = util.session.query(Cache).filter_by(descr=self.id,
option=opt.id).first()
if ret is None:
raise AttributeError(_('no option {0} found').format(opt))
return ret.path
@ -374,30 +359,36 @@ class StorageOptionDescription(object):
def impl_get_group_type(self):
return getattr(groups, self._group_type)
def impl_build_cache_option(self, descr=None, _currpath=None):
def impl_build_cache_option(self, descr=None, _currpath=None,
subdyn_path=None):
if descr is None:
save = True
descr = self
_currpath = []
else:
save = False
for option in self.impl_getchildren():
for option in self._impl_getchildren(dyn=False):
attr = option.impl_getname()
session.add(Cache(descr, option,
str('.'.join(_currpath + [attr]))))
util.session.add(Cache(descr, self, option,
str('.'.join(_currpath + [attr])), subdyn_path))
if isinstance(option, StorageOptionDescription):
if option.impl_is_dynoptiondescription():
subdyn_path = '.'.join(_currpath)
_currpath.append(attr)
option.impl_build_cache_option(descr,
_currpath)
_currpath,
subdyn_path)
_currpath.pop()
if save:
self._is_build_cache = True
session.commit()
util.session.commit()
def impl_get_options_paths(self, bytype, byname, _subpath, only_first):
sqlquery = session.query(Cache).filter_by(descr=self.id)
def impl_get_options_paths(self, bytype, byname, _subpath, only_first,
context):
sqlquery = util.session.query(Cache).filter_by(descr=self.id)
if bytype is None:
sqlquery = sqlquery.filter(not_(Cache.opt_type == 'OptionDescription'))
sqlquery = sqlquery.filter(not_(
Cache.opt_type == 'OptionDescription'))
else:
sqlquery = sqlquery.filter_by(opt_type=bytype.__name__)
@ -413,46 +404,111 @@ class StorageOptionDescription(object):
if or_query != '':
filter_query = or_(Cache.path == or_query, filter_query)
sqlquery = sqlquery.filter(filter_query)
if only_first:
opt = sqlquery.first()
if opt is None:
return tuple()
option = session.query(_Base).filter_by(id=opt.option).first()
return ((opt.path, option),)
else:
ret = []
for opt in sqlquery.all():
option = session.query(_Base).filter_by(id=opt.option).first()
ret.append((opt.path, option))
return ret
#if only_first:
# opt = sqlquery.first()
# if opt is None:
# return tuple()
# option = util.session.query(_Base).filter_by(id=opt.option).first()
# return ((opt.path, option),)
#else:
ret = []
for opt in sqlquery.all():
option = util.session.query(_Base).filter_by(id=opt.option).first()
if opt.is_subdyn:
name = option.impl_getname()
if byname is not None and byname.startswith(name):
found = False
for suffix in option._subdyn._impl_get_suffixes(
context):
if byname == name + suffix:
found = True
subdyn_path = opt.subdyn_path
dynpaths = opt.path[len(subdyn_path):].split('.')
path = subdyn_path
for dynpath in dynpaths:
path += '.' + dynpath + suffix
option = option._impl_to_dyn(
name + suffix, path)
break
if not found:
break
else:
ret_opt = (opt.path, option)
if only_first:
return ret_opt
ret.append(ret_opt)
return ret
def _add_children(self, child_names, children):
for child in children:
session.add(_Parent(self, child))
util.session.add(_Parent(self, child))
def impl_getchildren(self):
for child in session.query(_Parent).filter_by(parent_id=self.id).all():
yield(session.query(_Base).filter_by(id=child.child_id).first())
#return
def _impl_st_getchildren(self, context, only_dyn=False):
if only_dyn is False or context is undefined:
for child in util.session.query(_Parent).filter_by(
parent_id=self.id).all():
yield(util.session.query(_Base).filter_by(id=child.child_id
).first())
else:
descr = context.cfgimpl_get_description().id
for child in util.session.query(Cache).filter_by(descr=descr,
parent=self.id
).filter_by(
is_subdyn=True).all():
yield(util.session.query(_Base).filter_by(id=child.option).first())
def __getattr__(self, name):
if name.startswith('_') or name.startswith('impl_'):
return object.__getattribute__(self, name)
child = session.query(_Parent).filter_by(parent_id=self.id, child_name=name).first()
if child is None:
raise AttributeError(_('unknown Option {0} '
'in OptionDescription {1}'
'').format(name, self.impl_getname()))
return session.query(_Base).filter_by(id=child.child_id).first()
def _getattr(self, name, suffix=undefined, context=undefined, dyn=True):
error = False
if suffix is not undefined:
try:
if undefined in [suffix, context]: # pragma: optional cover
raise ConfigError(_("suffix and context needed if "
"it's a dyn option"))
if name.endswith(suffix):
oname = name[:-len(suffix)]
#child = self._children[1][self._children[0].index(oname)]
child = util.session.query(_Parent).filter_by(
parent_id=self.id, child_name=oname).first()
if child is None:
error = True
else:
opt = util.session.query(_Base).filter_by(
id=child.child_id).first()
return self._impl_get_dynchild(opt, suffix)
else:
error = True
except ValueError: # pragma: optional cover
error = True
else:
child = util.session.query(_Parent).filter_by(parent_id=self.id,
child_name=name
).first()
if child is None:
child = self._impl_search_dynchild(name, context=context)
if child != []:
return child
error = True
if error is False:
return util.session.query(_Base).filter_by(id=child.child_id
).first()
if error:
raise AttributeError(_('unknown Option {0} in OptionDescription {1}'
'').format(name, self.impl_getname()))
def _get_force_store_value(self):
#only option in current tree
current_ids = tuple(chain(*util.session.query(Cache.option).filter_by(
descr=self.id).all()))
for prop in util.session.query(_PropertyOption).filter(
_PropertyOption.option.in_(current_ids),
_PropertyOption.name == 'force_store_value').all():
opt = util.session.query(_Base).filter_by(id=prop.option).first()
path = self.impl_get_path_by_opt(opt)
yield (opt, path)
class StorageBase(_Base):
@declared_attr
def __mapper_args__(self):
return {'polymorphic_identity': self.__name__.lower()}
#engine.echo = True
SqlAlchemyBase.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()

View file

@ -80,8 +80,6 @@ class Values(object):
# if value has callback and is not set
if opt.impl_has_callback():
callback, callback_params = opt.impl_get_callback()
if callback_params is None:
callback_params = {}
value = carry_out_calculation(opt, config=self._getcontext(),
callback=callback,
callback_params=callback_params,
@ -126,8 +124,8 @@ class Values(object):
def get_modified_values(self):
context = self._getcontext()
if context._impl_descr is not None:
for opt, path in context.cfgimpl_get_settings(
).get_with_property('force_store_value'):
for opt, path in context.cfgimpl_get_description(
)._get_force_store_value():
self._getowner(opt, path, force_permissive=True)
return self._p_.get_modified_values()
@ -554,7 +552,6 @@ class Multi(list):
self)
#def __repr__(self, *args, **kwargs):
# print args, kwargs
# return super(Multi, self).__repr__(*args, **kwargs)
#def __getitem__(self, y):