From 71f8926fca848dcecda3cf1b1acad61c43598440 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Sun, 6 Jul 2014 15:31:57 +0200 Subject: [PATCH] better support for sqlalchemy storage --- test/test_dyn_optiondescription.py | 34 +-- test/test_option_setting.py | 2 +- tiramisu/autolib.py | 3 +- tiramisu/option/baseoption.py | 75 ++++--- tiramisu/option/masterslave.py | 2 +- tiramisu/option/option.py | 17 +- tiramisu/option/optiondescription.py | 30 ++- tiramisu/setting.py | 44 ++-- tiramisu/storage/__init__.py | 11 +- tiramisu/storage/dictionary/__init__.py | 6 +- tiramisu/storage/dictionary/option.py | 65 +++--- tiramisu/storage/dictionary/setting.py | 3 +- tiramisu/storage/dictionary/value.py | 2 +- tiramisu/storage/sqlalchemy/__init__.py | 37 ++++ tiramisu/storage/sqlalchemy/option.py | 272 ++++++++++++++---------- tiramisu/value.py | 7 +- 16 files changed, 369 insertions(+), 241 deletions(-) diff --git a/test/test_dyn_optiondescription.py b/test/test_dyn_optiondescription.py index 446d160..cdb6ce1 100644 --- a/test/test_dyn_optiondescription.py +++ b/test/test_dyn_optiondescription.py @@ -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") diff --git a/test/test_option_setting.py b/test/test_option_setting.py index cd38a48..193955e 100644 --- a/test/test_option_setting.py +++ b/test/test_option_setting.py @@ -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']"] diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index 41ad226..44bb82e 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -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) diff --git a/tiramisu/option/baseoption.py b/tiramisu/option/baseoption.py index 06f4bb4..8eec7fa 100644 --- a/tiramisu/option/baseoption.py +++ b/tiramisu/option/baseoption.py @@ -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',) diff --git a/tiramisu/option/masterslave.py b/tiramisu/option/masterslave.py index f7dadf9..ee72ec1 100644 --- a/tiramisu/option/masterslave.py +++ b/tiramisu/option/masterslave.py @@ -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): diff --git a/tiramisu/option/option.py b/tiramisu/option/option.py index 48541b4..1f5df9d 100644 --- a/tiramisu/option/option.py +++ b/tiramisu/option/option.py @@ -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, diff --git a/tiramisu/option/optiondescription.py b/tiramisu/option/optiondescription.py index f8f3f75..f9e9401 100644 --- a/tiramisu/option/optiondescription.py +++ b/tiramisu/option/optiondescription.py @@ -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 diff --git a/tiramisu/setting.py b/tiramisu/setting.py index e5871ad..d2435f2 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -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)} diff --git a/tiramisu/storage/__init__.py b/tiramisu/storage/__init__.py index 5bb0ec8..5e51f1d 100644 --- a/tiramisu/storage/__init__.py +++ b/tiramisu/storage/__init__.py @@ -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 diff --git a/tiramisu/storage/dictionary/__init__.py b/tiramisu/storage/dictionary/__init__.py index e6f358d..a52bf26 100644 --- a/tiramisu/storage/dictionary/__init__.py +++ b/tiramisu/storage/dictionary/__init__.py @@ -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) diff --git a/tiramisu/storage/dictionary/option.py b/tiramisu/storage/dictionary/option.py index 38a739d..3cb61e7 100644 --- a/tiramisu/storage/dictionary/option.py +++ b/tiramisu/storage/dictionary/option.py @@ -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) diff --git a/tiramisu/storage/dictionary/setting.py b/tiramisu/storage/dictionary/setting.py index a25c989..fb76109 100644 --- a/tiramisu/storage/dictionary/setting.py +++ b/tiramisu/storage/dictionary/setting.py @@ -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) diff --git a/tiramisu/storage/dictionary/value.py b/tiramisu/storage/dictionary/value.py index 2876d72..f0ffe3a 100644 --- a/tiramisu/storage/dictionary/value.py +++ b/tiramisu/storage/dictionary/value.py @@ -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 diff --git a/tiramisu/storage/sqlalchemy/__init__.py b/tiramisu/storage/sqlalchemy/__init__.py index e69de29..827cd2f 100644 --- a/tiramisu/storage/sqlalchemy/__init__.py +++ b/tiramisu/storage/sqlalchemy/__init__.py @@ -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 . +# ____________________________________________________________ +"""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) diff --git a/tiramisu/storage/sqlalchemy/option.py b/tiramisu/storage/sqlalchemy/option.py index 96673ad..a2c103f 100644 --- a/tiramisu/storage/sqlalchemy/option.py +++ b/tiramisu/storage/sqlalchemy/option.py @@ -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() diff --git a/tiramisu/value.py b/tiramisu/value.py index 23dbaae..a6e30be 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -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):