This commit is contained in:
2017-11-20 17:01:36 +01:00
parent 5d1be8a11a
commit 119ca85041
11 changed files with 1141 additions and 1199 deletions

View File

@ -106,7 +106,8 @@ rw_append = set(['frozen', 'disabled', 'validator', 'hidden'])
rw_remove = set(['permissive', 'everything_frozen', 'mandatory', 'empty'])
forbidden_set_properties = set(['force_store_value'])
forbidden_set_properties = frozenset(['force_store_value'])
forbidden_set_permissives = frozenset(['frozen', 'force_default_on_freeze'])
log = getLogger('tiramisu')
@ -124,15 +125,16 @@ class _NameSpace(object):
when attribute is added, we cannot delete it
"""
def __setattr__(self, name, value):
if name in self.__dict__: # pragma: optional cover
def __setattr__(self,
name,
value):
if name in self.__dict__:
raise ConstError(_("can't rebind {0}").format(name))
self.__dict__[name] = value
def __delattr__(self, name): # pragma: optional cover
if name in self.__dict__:
raise ConstError(_("can't unbind {0}").format(name))
raise ValueError(name)
def __delattr__(self,
name):
raise ConstError(_("can't unbind {0}").format(name))
class GroupModule(_NameSpace):
@ -168,69 +170,44 @@ class OwnerModule(_NameSpace):
"""groups that are default (typically 'default')"""
pass
class MultiTypeModule(_NameSpace):
"namespace for the master/slaves"
class MultiType(str):
pass
class DefaultMultiType(MultiType):
pass
class MasterMultiType(MultiType):
pass
class SlaveMultiType(MultiType):
pass
# ____________________________________________________________
def populate_groups():
"""populates the available groups in the appropriate namespaces
groups.default
default group set when creating a new optiondescription
groups.master
master group is a special optiondescription, all suboptions should be
multi option and all values should have same length, to find master's
option, the optiondescription's name should be same than de master's
option
groups.family
example of group, no special behavior with this group's type
"""
groups.default = groups.DefaultGroupType('default')
groups.master = groups.MasterGroupType('master')
groups.family = groups.GroupType('family')
def populate_owners():
"""populates the available owners in the appropriate namespaces
default
is the config owner after init time
user
is the generic is the generic owner
"""
setattr(owners, 'default', owners.DefaultOwner('default'))
setattr(owners, 'user', owners.Owner('user'))
setattr(owners, 'forced', owners.Owner('forced'))
def addowner(name):
def addowner(self, name):
"""
:param name: the name of the new owner
"""
setattr(owners, name, owners.Owner(name))
setattr(owners, 'addowner', addowner)
# ____________________________________________________________
# populate groups and owners with default attributes
# populate groups
groups = GroupModule()
populate_groups()
"""groups.default
default group set when creating a new optiondescription"""
groups.default = groups.DefaultGroupType('default')
"""groups.master
master group is a special optiondescription, all suboptions should be
multi option and all values should have same length, to find master's
option, the optiondescription's name should be same than de master's
option"""
groups.master = groups.MasterGroupType('master')
""" groups.family
example of group, no special behavior with this group's type"""
groups.family = groups.GroupType('family')
# ____________________________________________________________
# populate owners with default attributes
owners = OwnerModule()
populate_owners()
"""default
is the config owner after init time"""
owners.default = owners.DefaultOwner('default')
"""user
is the generic is the generic owner"""
owners.user = owners.Owner('user')
"""forced
special owner when value is forced"""
owners.forced = owners.Owner('forced')
# ____________________________________________________________
@ -241,73 +218,6 @@ class Undefined(object):
undefined = Undefined()
# ____________________________________________________________
class Property(object):
"a property is responsible of the option's value access rules"
__slots__ = ('_setting', '_properties', '_opt', '_path')
def __init__(self, setting, prop, opt=None, path=None):
self._opt = opt
self._path = path
self._setting = setting
self._properties = prop
def append(self, propname):
"""Appends a property named propname
:param propname: a predefined or user defined property name
:type propname: string
"""
self._append(propname)
def _append(self, propname, save=True):
if self._opt is not None and self._opt.impl_getrequires() is not None \
and propname in getattr(self._opt, '_calc_properties', static_set): # pragma: optional cover
raise ValueError(_('cannot append {0} property for option {1}: '
'this property is calculated').format(
propname, self._opt.impl_getname()))
if propname in forbidden_set_properties:
raise ConfigError(_('cannot add those properties: {0}').format(propname))
self._properties.add(propname)
if save:
self._setting.setproperties(self._properties, self._opt, self._path, force=True)
def remove(self, propname):
"""Removes a property named propname
:param propname: a predefined or user defined property name
:type propname: string
"""
if propname in self._properties:
self._properties.remove(propname)
self._setting.setproperties(self._properties, self._opt, self._path)
def extend(self, propnames):
"""Extends properties to the existing properties
:param propnames: an iterable made of property names
:type propnames: iterable of string
"""
for propname in propnames:
self._append(propname, save=False)
self._setting.setproperties(self._properties, self._opt, self._path)
def reset(self):
"""resets the properties (does not **clear** the properties,
default properties are still present)
"""
self._setting.reset(_path=self._path)
def __contains__(self, propname):
return propname in self._properties
def __repr__(self):
return str(list(self._properties))
def get(self):
return tuple(self._properties)
#____________________________________________________________
class Settings(object):
"``config.Config()``'s configuration options settings"
@ -341,321 +251,89 @@ class Settings(object):
return context
#____________________________________________________________
# properties methods
def __contains__(self, propname):
"enables the pythonic 'in' syntaxic sugar"
return propname in self._getproperties(read_write=False)
# get properties and permissive methods
def __repr__(self):
return str(list(self._getproperties(read_write=False)))
def __getitem__(self, opt):
path = opt.impl_getpath(self._getcontext())
return self.getproperties(opt, path)
def get_context_properties(self):
ntime = int(time())
if self._p_.hascache(None,
None):
is_cached, props = self._p_.getcache(None,
ntime,
None)
else:
is_cached = False
if not is_cached or 'cache' not in props:
meta = self._getcontext().cfgimpl_get_meta()
if meta is None:
props = self._p_.getproperties(None,
default_properties)
else:
props = meta.cfgimpl_get_settings().get_context_properties()
if 'cache' in props:
if 'expire' in props:
ntime = ntime + expires_time
else:
ntime = None
self._p_.setcache(None, props, ntime, None)
return props
def getproperties(self,
opt,
path,
setting_properties=undefined,
setting_properties,
index=None,
obj=True):
"""get properties for a specified option
"""
properties = self._getproperties(opt,
path,
index=index,
setting_properties=setting_properties)
if obj:
return Property(self, properties, opt, path)
return properties
def get_context_properties(self):
return self._getproperties()
def __setitem__(self, opt, value): # pragma: optional cover
raise ValueError(_('you should only append/remove properties'))
def reset(self, opt=None, _path=None, all_properties=False):
if all_properties and (_path or opt): # pragma: optional cover
raise ValueError(_('opt and all_properties must not be set '
'together in reset'))
if all_properties:
self._p_.reset_all_properties()
else:
if opt is not None and _path is None:
_path = opt.impl_getpath(self._getcontext())
self._p_.delproperties(_path)
self._getcontext().cfgimpl_reset_cache(opt=opt, path=_path, only=('settings', 'values'))
def _getproperties(self,
opt=None,
path=None,
setting_properties=undefined,
read_write=True,
apply_requires=True,
index=None):
apply_requires=True):
"""
"""
if opt is None:
ntime = int(time())
if self._p_.hascache(path, index):
is_cached, props = self._p_.getcache(path, ntime, None)
else:
is_cached = False
if not is_cached or 'cache' not in props:
meta = self._getcontext().cfgimpl_get_meta()
if meta is None:
props = self._p_.getproperties(path, default_properties)
else:
props = meta.cfgimpl_get_settings()._getproperties()
if 'cache' in props:
if 'expire' in props:
ntime = ntime + expires_time
else:
ntime = None
self._p_.setcache(path, props, ntime, None)
else:
if path is None: # pragma: optional cover
raise ValueError(_('if opt is not None, path should not be'
' None in _getproperties'))
if setting_properties is undefined:
setting_properties = self._getproperties(read_write=False)
is_cached = False
is_cached = False
if apply_requires:
if 'cache' in setting_properties and 'expire' in setting_properties:
ntime = int(time())
else:
ntime = None
if 'cache' in setting_properties and self._p_.hascache(path, index):
is_cached, props = self._p_.getcache(path, ntime, index)
if not is_cached:
props = self._p_.getproperties(path, opt.impl_getproperties())
if not opt.impl_is_optiondescription() and opt.impl_is_multi() and \
not opt.impl_is_master_slaves('slave'):
props.add('empty')
if apply_requires:
requires = self.apply_requires(opt, path, setting_properties, index, False)
if requires != set([]):
props = copy(props)
props |= requires
if 'cache' in setting_properties:
if 'expire' in setting_properties:
ntime = ntime + expires_time
self._p_.setcache(path, props, ntime, index)
if read_write:
props = copy(props)
return props
def append(self, propname):
"puts property propname in the Config's properties attribute"
props = self._p_.getproperties(None, default_properties)
if propname not in props:
props.add(propname)
self.setproperties(props, None, 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)
def extend(self, propnames):
for propname in propnames:
self.append(propname)
def _setproperties(self, properties, opt, path, force=False):
"""just for compatibility
"""
self.setproperties(properties, opt, path, force)
def setproperties(self, properties, opt, path, force=False):
"""save properties for specified path
(never save properties if same has option properties)
"""
if self._getcontext().cfgimpl_get_meta() is not None:
raise ConfigError(_('cannot change global property with metaconfig'))
if not force:
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)
#values too because of slave values could have a PropertiesOptionError has value
self._getcontext().cfgimpl_reset_cache(opt=opt, path=path, only=('values', 'properties',))
def getpermissive(self, setting_properties, path=None):
if 'cache' in setting_properties and 'expire' in setting_properties:
ntime = int(time())
else:
ntime = None
if 'cache' in setting_properties and self._pp_.hascache(path, None):
is_cached, perm = self._pp_.getcache(path, ntime, None)
else:
is_cached = False
if not is_cached:
if path is not None:
perm = self._pp_.getpermissive(path)
else:
perm = self._pp_.getpermissive()
if 'cache' in setting_properties:
if 'expire' in setting_properties:
ntime = ntime + expires_time
self._pp_.setcache(path, perm, ntime, None)
return perm
#____________________________________________________________
def validate_properties(self, opt_or_descr, is_descr, check_frozen, path,
value=None, force_permissive=False,
setting_properties=undefined,
self_properties=undefined,
index=None, debug=False):
"""
validation upon the properties related to `opt_or_descr`
:param opt_or_descr: an option or an option description object
:param force_permissive: behaves as if the permissive property
was present
:param is_descr: we have to know if we are in an option description,
just because the mandatory property
doesn't exist here
:param check_frozen: in the validation process, an option is to be modified,
the behavior can be different
(typically with the `frozen` property)
"""
# opt properties
if setting_properties is undefined:
setting_properties = self._getproperties(read_write=False)
if self_properties is not undefined:
properties = copy(self_properties)
else:
properties = self._getproperties(opt_or_descr, path,
setting_properties=setting_properties,
index=index)
# calc properties
properties &= setting_properties
if not is_descr:
#mandatory
if 'mandatory' in properties and \
not self._getcontext().cfgimpl_get_values()._isempty(
opt_or_descr, value, index=index):
properties.remove('mandatory')
elif 'empty' in properties and \
'empty' in setting_properties and \
self._getcontext().cfgimpl_get_values()._isempty(
opt_or_descr, value, force_allow_empty_list=True, index=index):
properties.add('mandatory')
# should return 'frozen' only when tried to modify a value
if check_frozen and 'everything_frozen' in setting_properties:
properties.add('frozen')
elif 'frozen' in properties and not check_frozen:
properties.remove('frozen')
if 'empty' in properties:
properties.remove('empty')
# remove permissive properties
if properties != frozenset():
# remove opt permissive
# permissive affect option's permission with or without permissive
# global property
properties -= self.getpermissive(setting_properties, path)
# remove global permissive if need
if force_permissive is True or 'permissive' in setting_properties:
properties -= self.getpermissive(setting_properties)
# at this point an option should not remain in properties
if properties != frozenset():
props = list(properties)
datas = {'opt': opt_or_descr, 'path': path, 'setting_properties': setting_properties,
'index': index, 'debug': True}
if is_descr:
opt_type = 'optiondescription'
else:
opt_type = 'option'
if 'frozen' in properties:
raise PropertiesOptionError(_('cannot change the value for '
'option "{0}" this option is'
' frozen').format(
opt_or_descr.impl_getname()),
props,
self,
datas,
opt_type)
else:
if len(props) == 1:
prop_msg = _('property')
else:
prop_msg = _('properties')
raise PropertiesOptionError(_('cannot access to {0} "{1}" '
'because has {2} {3}'
'').format(opt_type,
opt_or_descr.impl_get_display_name(),
prop_msg,
display_list(props)),
props,
self,
datas,
opt_type)
def setpermissive(self, permissive, opt=None, path=None):
"""
enables us to put the permissives in the storage
:param path: the option's path
:param type: str
:param opt: if an option object is set, the path is extracted.
it is better (faster) to set the path parameter
instead of passing a :class:`tiramisu.option.Option()` object.
"""
if opt is not None and path is None:
path = opt.impl_getpath(self._getcontext())
if not isinstance(permissive, tuple): # pragma: optional cover
raise TypeError(_('permissive must be a tuple'))
self._pp_.setpermissive(path, permissive)
setting_properties = self._getproperties(read_write=False)
self._getcontext().cfgimpl_reset_cache(opt=opt, path=path, only=('properties', 'values'))
if 'cache' in setting_properties:
if 'expire' in setting_properties:
ntime = int(time()) + expires_time
if apply_requires:
if 'cache' in setting_properties and 'expire' in setting_properties:
ntime = int(time())
else:
ntime = None
self._pp_.setcache(path, set(permissive), ntime, None)
if 'cache' in setting_properties and self._p_.hascache(path,
index):
is_cached, props = self._p_.getcache(path,
ntime,
index)
if not is_cached:
props = self._p_.getproperties(path,
opt.impl_getproperties())
if apply_requires:
requires = self.apply_requires(opt,
path,
setting_properties,
index,
False)
#FIXME devrait etre un frozenset!
if requires != set([]):
props = copy(props)
props |= requires
#____________________________________________________________
def setowner(self, owner):
":param owner: sets the default value for owner at the Config level"
if not isinstance(owner, owners.Owner): # pragma: optional cover
raise TypeError(_("invalid generic owner {0}").format(str(owner)))
self._owner = owner
#FIXME qu'est ce qui se passe si pas de owner ??
props -= self.getpermissive(path)
if apply_requires and 'cache' in setting_properties:
if 'expire' in setting_properties:
ntime = ntime + expires_time
self._p_.setcache(path,
props,
ntime,
index)
return props
def getowner(self):
return self._owner
def get_context_permissive(self):
return self.getpermissive(None)
#____________________________________________________________
def _read(self, remove, append):
props = self._p_.getproperties(None, default_properties)
modified = False
if remove & props != set([]):
props = props - remove
modified = True
if append & props != append:
props = props | append
modified = True
if modified:
self.setproperties(props, None, None)
def getpermissive(self,
path):
return self._pp_.getpermissive(path)
def read_only(self):
"convenience method to freeze, hide and disable"
self._read(ro_remove, ro_append)
def read_write(self):
"convenience method to freeze, hide and disable"
self._read(rw_remove, rw_append)
def apply_requires(self, opt, path, setting_properties, index, debug):
def apply_requires(self,
opt,
path,
setting_properties,
index,
debug):
"""carries out the jit (just in time) requirements between options
a requirement is a tuple of this form that comes from the option's
@ -735,16 +413,16 @@ class Settings(object):
idx = None
try:
value = context.getattr(reqpath,
setting_properties,
force_permissive=True,
_setting_properties=setting_properties,
index=idx)
except PropertiesOptionError as err:
if not transitive:
if all_properties is None:
all_properties = []
for requires in opt.impl_getrequires():
for require in requires:
all_properties.append(require[1])
for requires_ in opt.impl_getrequires():
for require_ in requires_:
all_properties.append(require_[1])
if not set(err.proptype) - set(all_properties):
continue
properties = err.proptype
@ -794,8 +472,228 @@ class Settings(object):
break
return calc_properties
#____________________________________________________________
# set methods
def set_context_properties(self, properties):
self.setproperties(None, None, properties)
def setproperties(self,
opt,
path,
properties):
# force=False):
"""save properties for specified path
(never save properties if same has option properties)
"""
if self._getcontext().cfgimpl_get_meta() is not None:
raise ConfigError(_('cannot change global property with metaconfig'))
#if not force:
forbidden_properties = forbidden_set_properties & properties
if forbidden_properties:
raise ConfigError(_('cannot add those properties: {0}').format(
' '.join(forbidden_properties)))
if not isinstance(properties, frozenset):
raise TypeError(_('properties must be a frozenset'))
self._p_.setproperties(path,
properties)
#values too because of slave values could have a PropertiesOptionError has value
self._getcontext().cfgimpl_reset_cache(opt=opt,
path=path)
def set_context_permissive(self, permissive):
self.setpermissive(None, None, permissive)
def setpermissive(self,
opt,
path,
permissives):
"""
enables us to put the permissives in the storage
:param path: the option's path
:param type: str
:param opt: if an option object is set, the path is extracted.
it is better (faster) to set the path parameter
instead of passing a :class:`tiramisu.option.Option()` object.
"""
if not isinstance(permissives, frozenset):
raise TypeError(_('permissive must be a frozenset'))
forbidden_permissives = forbidden_set_permissives & permissives
if forbidden_permissives:
raise ConfigError(_('cannot add those permissives: {0}').format(
' '.join(forbidden_permissives)))
self._pp_.setpermissive(path, permissives)
self._getcontext().cfgimpl_reset_cache(opt=opt,
path=path)
#____________________________________________________________
# reset methods
def reset(self, opt=None, _path=None, all_properties=False):
if all_properties and (_path or opt): # pragma: optional cover
raise ValueError(_('opt and all_properties must not be set '
'together in reset'))
if all_properties:
self._p_.reset_all_properties()
else:
if opt is not None and _path is None:
_path = opt.impl_getpath(self._getcontext())
self._p_.delproperties(_path)
self._getcontext().cfgimpl_reset_cache(opt=opt,
path=_path)
#____________________________________________________________
# validate properties
def validate_properties(self,
opt_or_descr,
is_descr,
check_frozen,
path,
value=None,
force_permissive=False,
setting_properties=undefined,
self_properties=undefined,
index=None,
debug=False):
"""
validation upon the properties related to `opt_or_descr`
:param opt_or_descr: an option or an option description object
:param force_permissive: behaves as if the permissive property
was present
:param is_descr: we have to know if we are in an option description,
just because the mandatory property
doesn't exist here
:param check_frozen: in the validation process, an option is to be modified,
the behavior can be different
(typically with the `frozen` property)
"""
# opt properties
if self_properties is not undefined:
if not isinstance(self_properties, frozenset):
raise Exception('pouet')
properties = self_properties
else:
properties = self.getproperties(opt_or_descr,
path,
setting_properties=setting_properties,
index=index)
# calc properties
properties &= setting_properties
if not is_descr:
#mandatory
if 'mandatory' in properties and \
not self._getcontext().cfgimpl_get_values().isempty(opt_or_descr,
value,
index=index):
properties.remove('mandatory')
elif 'empty' in properties and \
'empty' in setting_properties and \
self._getcontext().cfgimpl_get_values().isempty(opt_or_descr,
value,
force_allow_empty_list=True,
index=index):
properties.add('mandatory')
# should return 'frozen' only when tried to modify a value
if check_frozen and 'everything_frozen' in setting_properties:
properties.add('frozen')
elif 'frozen' in properties and not check_frozen:
properties.remove('frozen')
if 'empty' in properties:
properties.remove('empty')
# remove permissive properties
if properties != frozenset() and (force_permissive is True or
'permissive' in setting_properties):
# remove global permissive if need
properties -= self.get_context_permissive()
# at this point an option should not remain in properties
if properties != frozenset():
props = list(properties)
datas = {'opt': opt_or_descr,
'path': path,
'setting_properties': setting_properties,
'index': index,
'debug': True}
if is_descr:
opt_type = 'optiondescription'
else:
opt_type = 'option'
if 'frozen' in properties:
raise PropertiesOptionError(_('cannot change the value for '
'option "{0}" this option is'
' frozen').format(
opt_or_descr.impl_getname()),
props,
self,
datas,
opt_type)
else:
if len(props) == 1:
prop_msg = _('property')
else:
prop_msg = _('properties')
raise PropertiesOptionError(_('cannot access to {0} "{1}" '
'because has {2} {3}'
'').format(opt_type,
opt_or_descr.impl_get_display_name(),
prop_msg,
display_list(props)),
props,
self,
datas,
opt_type)
#____________________________________________________________
# read only/read write
def _read(self,
remove,
append):
props = self._p_.getproperties(None,
default_properties)
modified = False
if remove & props != set([]):
props = props - remove
modified = True
if append & props != append:
props = props | append
modified = True
if modified:
self.set_context_properties(frozenset(props))
def read_only(self):
"convenience method to freeze, hide and disable"
self._read(ro_remove,
ro_append)
def read_write(self):
"convenience method to freeze, hide and disable"
self._read(rw_remove,
rw_append)
#____________________________________________________________
# get modified properties/permissives
def get_modified_properties(self):
return self._p_.get_modified_properties()
def get_modified_permissives(self):
return self._pp_.get_modified_permissives()
#____________________________________________________________
# default owner methods
def setowner(self,
owner):
":param owner: sets the default value for owner at the Config level"
if not isinstance(owner,
owners.Owner): # pragma: optional cover
raise TypeError(_("invalid generic owner {0}").format(str(owner)))
self._owner = owner
def getowner(self):
return self._owner