add index to properties/permissives

This commit is contained in:
Emmanuel Garette 2019-11-19 18:39:44 +01:00
parent 15669372db
commit 7325f6e12f
5 changed files with 183 additions and 134 deletions

View file

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

View file

@ -15,6 +15,7 @@
# 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/>.
# ____________________________________________________________
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,6 +112,7 @@ FORBIDDEN_SET_PERMISSIVES = frozenset(['force_default_on_freeze',
'force_metaconfig_on_freeze',
'force_store_value'])
ALLOWED_LEADER_PROPERTIES = frozenset(['empty',
'unique',
'force_store_value',
'mandatory',
'force_default_on_freeze',
@ -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():
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()
return self._pp_.getpermissives(path)
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:

View file

@ -15,7 +15,7 @@
# 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/>.
# ____________________________________________________________
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])

View file

@ -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._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),
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, permissives, session_id) "
"VALUES (?, ?, ?)", (path,
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)

View file

@ -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, '\