diff --git a/tiramisu/api.py b/tiramisu/api.py index 0c56245..943ab49 100644 --- a/tiramisu/api.py +++ b/tiramisu/api.py @@ -17,6 +17,7 @@ from inspect import ismethod, getdoc, signature from time import time from typing import List, Set, Any, Optional, Callable, Union, Dict +from warnings import catch_warnings, simplefilter from .error import APIError, ConfigError, LeadershipError, PropertiesOptionError, ValueErrorWarning @@ -134,7 +135,7 @@ class CommonTiramisuOption(CommonTiramisu): if not option.impl_is_optiondescription() and \ self._option_bag.index is None and \ option.impl_is_follower(): - raise APIError(_('index must be set with the follower option "{}"').format(self._option_bag.path)) + raise APIError(_('index must be set with the follower option "{}"').format(self._option_bag.option.impl_get_display_name())) def __getattr__(self, name): raise APIError(_('unknown method {} in {}').format(name, self.__class__.__name__)) @@ -389,6 +390,7 @@ class TiramisuOptionProperty(CommonTiramisuOption): raise ConfigError(_('cannot add this property: "{0}"').format( ' '.join(prop))) props = self._settings._p_.getproperties(self._option_bag.path, + self._option_bag.index, option.impl_getproperties()) self._settings.setproperties(self._option_bag.path, props | {prop}, @@ -399,6 +401,7 @@ class TiramisuOptionProperty(CommonTiramisuOption): """Remove new property for an option""" option = self._option_bag.option props = self._settings._p_.getproperties(self._option_bag.path, + self._option_bag.index, option.impl_getproperties()) self._settings.setproperties(self._option_bag.path, props - {prop}, @@ -430,8 +433,7 @@ class TiramisuOptionPermissive(CommonTiramisuOption): def get(self): """Get permissives value""" - return self._settings.getpermissives(self._option_bag.option, - self._option_bag.path) + return self._settings.getpermissives(self._option_bag) def set(self, permissives): """Set permissives value""" @@ -528,7 +530,8 @@ class _TiramisuOptionValueOption: def valid(self): try: - with warnings.catch_warnings(record=True) as warns: + with catch_warnings(record=True) as warns: + simplefilter("always", ValueErrorWarning) self.get() for warn in warns: if isinstance(warns.message, ValueErrorWarning): @@ -538,6 +541,13 @@ class _TiramisuOptionValueOption: return True +class _TiramisuOptionValueChoiceOption: + def list(self): + """All values available for a ChoiceOption""" + option = self._option_bag.option + return option.impl_get_values(self._option_bag) + + class _TiramisuOptionValueLeader: def pop(self, index): """Pop a value""" @@ -556,12 +566,6 @@ class _TiramisuOptionValueLeader: return self._length -class _TiramisuOptionValueGroup: - def reset(self): - """Reset value""" - self._option_bag.config_bag.context.reset(self._option_bag.path) - - class _TiramisuOptionValueFollower: def len(self): """Length of follower option""" @@ -572,16 +576,10 @@ class _TiramisuOptionValueFollower: return self._length -class _TiramisuOptionValueChoiceOption: - def list(self): - """All values available for a ChoiceOption""" - option = self._option_bag.option - return option.impl_get_values(self._option_bag) - - def callbacks(self): - """Get callbacks for a values""" - option = self._option_bag.option - return option.get_callback() +class _TiramisuOptionValueGroup: + def reset(self): + """Reset value""" + self._option_bag.config_bag.context.reset(self._option_bag.path) class _TiramisuOptionValueOptionDescription: @@ -901,6 +899,7 @@ class TiramisuContextValue(TiramisuConfig): """Return path of options with mandatory property without any value""" return self._config_bag.context.cfgimpl_get_values().mandatory_warnings(self._config_bag) + # FIXME should be only for group/meta def set(self, path: str, value, @@ -925,6 +924,7 @@ class TiramisuContextValue(TiramisuConfig): self._config_bag, **kwargs) + # FIXME should be only for group/meta def reset(self, path: str, only_children: bool=False): @@ -1026,20 +1026,20 @@ class TiramisuContextProperty(TiramisuConfig): """Add a config property""" props = set(self.get()) props.add(prop) - self.set(frozenset(props)) + self._set(frozenset(props)) def pop(self, prop): """Remove a config property""" props = set(self.get()) if prop in props: props.remove(prop) - self.set(frozenset(props)) + self._set(frozenset(props)) def get(self): """Get all config properties""" return self._config_bag.properties - def set(self, props): + def _set(self, props): """Personalise config properties""" if 'force_store_value' in props: force_store_value = 'force_store_value' not in self._config_bag.properties @@ -1065,7 +1065,7 @@ class TiramisuContextProperty(TiramisuConfig): def importation(self, properties): """Import config properties""" - if 'force_store_value' in properties.get(None, []): + if 'force_store_value' in properties.get(None, {}).get(None, []): force_store_value = 'force_store_value' not in self._config_bag.properties else: force_store_value = False @@ -1134,7 +1134,7 @@ class TiramisuContextPermissive(TiramisuConfig): """Get config permissives""" return self._config_bag.context.cfgimpl_get_settings().get_context_permissives() - def set(self, permissives): + def _set(self, permissives): """Set config permissives""" self._config_bag.context.cfgimpl_get_settings().set_context_permissives(permissives) del self._config_bag.permissives @@ -1161,14 +1161,14 @@ class TiramisuContextPermissive(TiramisuConfig): """Add a config permissive""" props = set(self.get()) props.add(prop) - self.set(frozenset(props)) + self._set(frozenset(props)) def pop(self, prop): """Remove a config permissive""" props = set(self.get()) if prop in props: props.remove(prop) - self.set(frozenset(props)) + self._set(frozenset(props)) class TiramisuContextOption(TiramisuConfig): diff --git a/tiramisu/setting.py b/tiramisu/setting.py index 252455a..4cf5161 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -15,6 +15,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ____________________________________________________________ +from itertools import chain from .error import PropertiesOptionError, ConstError, ConfigError, LeadershipError, display_list from .i18n import _ @@ -42,11 +43,6 @@ frozen cannot set value for option with this properties if 'frozen' is set in config -mandatory - should set value for option with this properties if 'mandatory' is set in - config - - * Special property: permissive @@ -55,6 +51,16 @@ permissive config with 'permissive', whole option in this config cannot raise PropertiesOptionError for properties set in permissive +mandatory + should set value for option with this properties if 'mandatory' is set in + config + +empty + raise mandatory PropertiesOptionError if multi or leader have empty value + +unique + raise ValueError if a value is set twice or more in a multi Option + * Special Config properties: cache @@ -67,9 +73,6 @@ everything_frozen whole option in config are frozen (even if option have not frozen property) -empty - raise mandatory PropertiesOptionError if multi or leader have empty value - validator launch validator set by user in option (this property has no effect for internal validator) @@ -109,11 +112,12 @@ FORBIDDEN_SET_PERMISSIVES = frozenset(['force_default_on_freeze', 'force_metaconfig_on_freeze', 'force_store_value']) ALLOWED_LEADER_PROPERTIES = frozenset(['empty', - 'force_store_value', - 'mandatory', - 'force_default_on_freeze', - 'force_metaconfig_on_freeze', - 'frozen']) + 'unique', + 'force_store_value', + 'mandatory', + 'force_default_on_freeze', + 'force_metaconfig_on_freeze', + 'frozen']) static_set = frozenset() @@ -405,6 +409,7 @@ class Settings(object): 'context_props') if not is_cached: props = self._p_.getproperties(None, + None, self.default_properties) cache.setcache(None, None, @@ -416,11 +421,9 @@ class Settings(object): def getproperties(self, option_bag, - apply_requires=True, - search_properties=None): + apply_requires=True): """ """ - # FIXME search_properties option = option_bag.option config_bag = option_bag.config_bag if option.impl_is_symlinkoption(): @@ -430,25 +433,32 @@ class Settings(object): if apply_requires: cache = config_bag.context._impl_properties_cache - props = config_bag.properties + config_bag_props = config_bag.properties is_cached, props, validated = cache.getcache(path, config_bag.expiration_time, index, - props, + config_bag_props, {}, 'self_props') else: is_cached = False if not is_cached: props = set() - for prop in self._p_.getproperties(path, - option.impl_getproperties()): + p_props = self._p_.getproperties(path, + None, + option.impl_getproperties()) + if index is not None: + p_props = chain(p_props, + self._p_.getproperties(path, + index, + option.impl_getproperties())) + for prop in p_props: if isinstance(prop, str): props.add(prop) elif apply_requires: new_prop = prop.execute(option_bag, leadership_must_have_index=True) - if not new_prop: + if new_prop is None: continue elif not isinstance(new_prop, str): raise ValueError(_('invalid property type {} for {} with {} function').format(type(new_prop), @@ -457,8 +467,7 @@ class Settings(object): if not option.impl_is_optiondescription() and option.impl_is_leader() and new_prop not in ALLOWED_LEADER_PROPERTIES: raise LeadershipError(_('leader cannot have "{}" property').format(new_prop)) props.add(new_prop) - props -= self.getpermissives(option, - path) + props -= self.getpermissives(option_bag) if apply_requires and not config_bag.is_unrestraint: cache.setcache(path, index, @@ -470,37 +479,61 @@ class Settings(object): def get_calculated_properties(self, option_bag): - opt = option_bag.option - if opt.impl_is_symlinkoption(): - opt = opt.impl_getopt() - path = opt.impl_getpath() - for prop in self._p_.getproperties(path, - opt.impl_getproperties()): + option = option_bag.option + if option.impl_is_symlinkoption(): + option = option.impl_getopt() + path = option.impl_getpath() + p_props = self._p_.getproperties(path, + None, + option.impl_getproperties()) + if option_bag.index is not None: + p_props = chain(p_props, + self._p_.getproperties(path, + option_bag.index, + option.impl_getproperties())) + for prop in p_props: if not isinstance(prop, str): yield prop def has_properties_index(self, option_bag): - opt = option_bag.option - if opt.impl_is_symlinkoption(): - opt = opt.impl_getopt() - path = opt.impl_getpath() - for prop in self._p_.getproperties(path, - opt.impl_getproperties()): - if not isinstance(prop, str) and prop.has_index: + option = option_bag.option + if option.impl_is_symlinkoption(): + option = option.impl_getopt() + path = option.impl_getpath() + p_props = self._p_.getproperties(path, + None, + option.impl_getproperties()) + if option_bag.index is not None: + p_props = chain(p_props, + self._p_.getproperties(path, + option_bag.index, + option.impl_getproperties())) + for prop in p_props: + if not isinstance(prop, str) and prop.has_index(option_bag.option): return True return False def get_context_permissives(self): - return self.getpermissives(None, None) + return self.getpermissives(None) def getpermissives(self, - opt, - path): - if opt and opt.impl_is_symlinkoption(): - opt = opt.impl_getopt() - path = opt.impl_getpath() - return self._pp_.getpermissives(path) + option_bag): + if option_bag is None: + path = None + index = None + else: + opt = option_bag.option + if opt.impl_is_symlinkoption(): + opt = opt.impl_getopt() + path = opt.impl_getpath() + else: + path = option_bag.path + index = option_bag.index + permissives = self._pp_.getpermissives(path, None) + if index is not None: + permissives = frozenset(self._pp_.getpermissives(path, index) | permissives) + return permissives #____________________________________________________________ # set methods @@ -508,6 +541,7 @@ class Settings(object): properties, context): self._p_.setproperties(None, + None, properties) context.cfgimpl_reset_cache(None) @@ -536,6 +570,7 @@ class Settings(object): '"force_default_on_freeze" or "force_metaconfig_on_freeze" property without "frozen"' '').format(opt.impl_get_display_name())) self._p_.setproperties(path, + option_bag.index, properties) # values too because of follower values could have a PropertiesOptionError has value context.cfgimpl_reset_cache(option_bag) @@ -566,13 +601,15 @@ class Settings(object): raise TypeError(_("can't assign permissive to the symlinkoption \"{}\"" "").format(opt.impl_get_display_name())) path = option_bag.path + index = option_bag.index else: path = None + index = None forbidden_permissives = FORBIDDEN_SET_PERMISSIVES & permissives if forbidden_permissives: raise ConfigError(_('cannot add those permissives: {0}').format( ' '.join(forbidden_permissives))) - self._pp_.setpermissives(path, permissives) + self._pp_.setpermissives(path, index, permissives) if option_bag is not None: option_bag.config_bag.context.cfgimpl_reset_cache(option_bag) @@ -585,13 +622,15 @@ class Settings(object): if option_bag is None: opt = None path = None + index = None else: opt = option_bag.option assert not opt.impl_is_symlinkoption(), _("can't reset properties to " "the symlinkoption \"{}\"" "").format(opt.impl_get_display_name()) path = option_bag.path - self._p_.delproperties(path) + index = option_bag.index + self._p_.delproperties(path, index) context.cfgimpl_reset_cache(option_bag) def reset_permissives(self, @@ -600,13 +639,15 @@ class Settings(object): if option_bag is None: opt = None path = None + index = None else: opt = option_bag.option assert not opt.impl_is_symlinkoption(), _("can't reset permissives to " "the symlinkoption \"{}\"" "").format(opt.impl_get_display_name()) + index = option_bag.index path = option_bag.path - self._pp_.delpermissive(path) + self._pp_.delpermissive(path, index) context.cfgimpl_reset_cache(option_bag) #____________________________________________________________ @@ -658,28 +699,27 @@ class Settings(object): option_bag): if 'mandatory' in option_bag.config_bag.properties: values = option_bag.config_bag.context.cfgimpl_get_values() - is_mandatory = False if not ('permissive' in option_bag.config_bag.properties and 'mandatory' in option_bag.config_bag.permissives) and \ 'mandatory' in option_bag.properties and values.isempty(option_bag.option, value, index=option_bag.index): - is_mandatory = True + raise PropertiesOptionError(option_bag, + ['mandatory'], + self) if 'empty' in option_bag.properties and values.isempty(option_bag.option, value, force_allow_empty_list=True, index=option_bag.index): - is_mandatory = True - if is_mandatory: raise PropertiesOptionError(option_bag, - ['mandatory'], + ['empty'], self) def validate_frozen(self, option_bag): if option_bag.config_bag.properties and \ ('everything_frozen' in option_bag.config_bag.properties or - 'frozen' in option_bag.properties) and \ + ('frozen' in option_bag.config_bag.properties and 'frozen' in option_bag.properties)) and \ not (('permissive' in option_bag.config_bag.properties) and 'frozen' in option_bag.config_bag.permissives): raise PropertiesOptionError(option_bag, @@ -694,6 +734,7 @@ class Settings(object): append, context): props = self._p_.getproperties(None, + None, self.default_properties) modified = False if remove & props: diff --git a/tiramisu/storage/dictionary/setting.py b/tiramisu/storage/dictionary/setting.py index 79a9cd6..15b98eb 100644 --- a/tiramisu/storage/dictionary/setting.py +++ b/tiramisu/storage/dictionary/setting.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ____________________________________________________________ -from copy import copy +from copy import deepcopy from ...log import log @@ -30,25 +30,29 @@ class Properties: self._storage = storage # properties - def setproperties(self, path, properties): - log.debug('setproperties %s %s', path, properties) - self._properties[path] = properties + def setproperties(self, path, index, properties): + log.debug('setproperties %s %s %s', path, index, properties) + self._properties.setdefault(path, {})[index] = properties - def getproperties(self, path, default_properties): - ret = self._properties.get(path, frozenset(default_properties)) - log.debug('getproperties %s %s', path, ret) + def getproperties(self, path, index, default_properties): + if path not in self._properties: + ret = frozenset(default_properties) + else: + ret = self._properties[path].get(index, frozenset(default_properties)) + log.debug('getproperties %s %s %s', path, index, ret) return ret - def delproperties(self, path): + def delproperties(self, path, index): log.debug('delproperties %s', path) - if path in self._properties: - del(self._properties[path]) + if path in self._properties and index in self._properties[path]: + del(self._properties[path][index]) + def exportation(self): """return all modified settings in a dictionary example: {'path1': set(['prop1', 'prop2'])} """ - return copy(self._properties) + return deepcopy(self._properties) def importation(self, properties): self._properties = properties @@ -63,29 +67,28 @@ class Permissives: self._permissives = {} self._storage = storage - def setpermissives(self, path, permissives): + def setpermissives(self, path, index, permissives): log.debug('setpermissives %s %s', path, permissives) - if not permissives: - if path in self._permissives: - del self._permissives[path] - else: - self._permissives[path] = permissives + self._permissives.setdefault(path, {})[index] = permissives - def getpermissives(self, path=None): - ret = self._permissives.get(path, frozenset()) + def getpermissives(self, path, index): + if not path in self._permissives: + ret = frozenset() + else: + ret = self._permissives[path].get(index, frozenset()) log.debug('getpermissives %s %s', path, ret) return ret + def delpermissive(self, path, index): + log.debug('delpermissive %s', path) + if path in self._permissives and index in self._permissives[path]: + del(self._permissives[path][index]) + def exportation(self): """return all modified permissives in a dictionary example: {'path1': set(['perm1', 'perm2'])} """ - return copy(self._permissives) + return deepcopy(self._permissives) def importation(self, permissives): self._permissives = permissives - - def delpermissive(self, path): - log.debug('delpermissive %s', path) - if path in self._permissives: - del(self._permissives[path]) diff --git a/tiramisu/storage/sqlite3/setting.py b/tiramisu/storage/sqlite3/setting.py index cce1445..0dca195 100644 --- a/tiramisu/storage/sqlite3/setting.py +++ b/tiramisu/storage/sqlite3/setting.py @@ -26,29 +26,30 @@ class Properties(Sqlite3DB): super(Properties, self).__init__(storage) # properties - def setproperties(self, path, properties): + def setproperties(self, path, index, properties): path = self._sqlite_encode_path(path) - self._storage.execute("DELETE FROM property WHERE path = ? AND session_id = ?", - (path, self._session_id), + self._storage.execute("DELETE FROM property WHERE path = ? AND tiram_index = ? AND session_id = ?", + (path, index, self._session_id), False) - self._storage.execute("INSERT INTO property(path, properties, session_id) VALUES " - "(?, ?, ?)", (path, - self._sqlite_encode(properties), - self._session_id)) + self._storage.execute("INSERT INTO property(path, tiram_index, properties, session_id) VALUES " + "(?, ?, ?, ?)", (path, + index, + self._sqlite_encode(properties), + self._session_id)) - def getproperties(self, path, default_properties): + def getproperties(self, path, index, default_properties): path = self._sqlite_encode_path(path) value = self._storage.select("SELECT properties FROM property WHERE " - "path = ? AND session_id = ? LIMIT 1", (path, self._session_id)) + "path = ? AND tiram_index = ? AND session_id = ? LIMIT 1", (path, index, self._session_id)) if value is None: return set(default_properties) else: return set(self._sqlite_decode(value[0])) - def delproperties(self, path): + def delproperties(self, path, index): path = self._sqlite_encode_path(path) - self._storage.execute("DELETE FROM property WHERE path = ? AND session_id = ?", - (path, self._session_id)) + self._storage.execute("DELETE FROM property WHERE path = ? AND tiram_index = ? AND session_id = ?", + (path, index, self._session_id)) def exportation(self): """return all modified settings in a dictionary @@ -79,22 +80,25 @@ class Permissives(Sqlite3DB): __slots__ = tuple() # permissive - def setpermissives(self, path, permissive): + def setpermissives(self, path, index, permissive): path = self._sqlite_encode_path(path) - log.debug('setpermissive %s %s %s', path, permissive, id(self)) - self._storage.execute("DELETE FROM permissive WHERE path = ? AND session_id = ?", - (path, self._session_id), - False) - self._storage.execute("INSERT INTO permissive(path, permissives, session_id) " - "VALUES (?, ?, ?)", (path, + log.debug('setpermissive %s %s %s %s', path, index, permissive, id(self)) + self._storage.execute("DELETE FROM permissive WHERE path = ? AND tiram_index = ? AND session_id = ?", + (path, + index, + self._session_id), + False) + self._storage.execute("INSERT INTO permissive(path, tiram_index, permissives, session_id) " + "VALUES (?, ?, ?, ?)", (path, + index, self._sqlite_encode(permissive), self._session_id)) - def getpermissives(self, path='_none'): + def getpermissives(self, path, index): path = self._sqlite_encode_path(path) permissives = self._storage.select("SELECT permissives FROM " - "permissive WHERE path = ? AND session_id = ? LIMIT 1", - (path, self._session_id)) + "permissive WHERE path = ? AND tiram_index = ? AND session_id = ? LIMIT 1", + (path, index, self._session_id)) if permissives is None: ret = frozenset() else: @@ -102,17 +106,17 @@ class Permissives(Sqlite3DB): log.debug('getpermissive %s %s %s', path, ret, id(self)) return ret - def delpermissive(self, path): + def delpermissive(self, path, index): path = self._sqlite_encode_path(path) - self._storage.execute("DELETE FROM permissive WHERE path = ? AND session_id = ?", - (path, self._session_id)) + self._storage.execute("DELETE FROM permissive WHERE path = ? AND tiram_index = ? AND session_id = ?", + (path, index, self._session_id)) def exportation(self): """return all modified permissives in a dictionary example: {'path1': set(['perm1', 'perm2'])} """ ret = {} - for path, permissives in self._storage.select("SELECT path, permissives FROM permissive " + for path, index, permissives in self._storage.select("SELECT path, tiram_index, permissives FROM permissive " "WHERE session_id = ?", (self._session_id,), only_one=False): @@ -125,8 +129,9 @@ class Permissives(Sqlite3DB): commit=False) for path, permissive in permissives.items(): path = self._sqlite_encode_path(path) - self._storage.execute("INSERT INTO permissive(path, permissives, session_id) " + self._storage.execute("INSERT INTO permissive(path, tiram_index, permissives, session_id) " "VALUES (?, ?, ?)", (path, + index, self._sqlite_encode(permissive), self._session_id, ), False) diff --git a/tiramisu/storage/sqlite3/storage.py b/tiramisu/storage/sqlite3/storage.py index 771516d..dcbd81f 100644 --- a/tiramisu/storage/sqlite3/storage.py +++ b/tiramisu/storage/sqlite3/storage.py @@ -105,11 +105,11 @@ class Storage(object): if init: session_table = 'CREATE TABLE IF NOT EXISTS session(session_id INTEGER, ' session_table += 'session TEXT UNIQUE, persistent BOOL, PRIMARY KEY(session_id))' - settings_table = 'CREATE TABLE IF NOT EXISTS property(path TEXT,' - settings_table += 'properties text, session_id INTEGER, PRIMARY KEY(path, session_id), ' + settings_table = 'CREATE TABLE IF NOT EXISTS property(path TEXT, ' + settings_table += 'tiram_index INTEGER, properties TEXT, session_id INTEGER, PRIMARY KEY(path, tiram_index, session_id), ' settings_table += 'FOREIGN KEY(session_id) REFERENCES session(session_id))' permissives_table = 'CREATE TABLE IF NOT EXISTS permissive(path TEXT,' - permissives_table += 'permissives TEXT, session_id INTEGER, PRIMARY KEY(path, session_id), ' + permissives_table += 'tiram_index INTEGER, permissives TEXT, session_id INTEGER, PRIMARY KEY(path, tiram_index, session_id), ' permissives_table += 'FOREIGN KEY(session_id) REFERENCES session(session_id))' values_table = 'CREATE TABLE IF NOT EXISTS value(path TEXT, ' values_table += 'value TEXT, owner TEXT, idx INTEGER, session_id INTEGER, '\