# -*- coding: utf-8 -*- # Copyright (C) 2014-2017 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 . # # The original `Config` design model is unproudly borrowed from # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ from copy import copy import re import weakref from ..i18n import _ from ..setting import groups, undefined, owners, log, debug from .baseoption import BaseOption from .option import Option, ALLOWED_CONST_LIST #from . import MasterSlaves from ..error import ConfigError, ConflictError, SlaveError, PropertiesOptionError from ..autolib import carry_out_calculation NAME_REGEXP = re.compile(r'^[a-zA-Z\d\-_]*$') import sys if sys.version_info[0] >= 3: # pragma: no cover xrange = range del(sys) class CacheOptionDescription(BaseOption): __slots__ = ('_cache_paths', '_cache_consistencies', '_cache_force_store_values') def _build_cache(self, config, path='', _consistencies=None, cache_option=None, force_store_values=None, _dependencies=None): """validate duplicate option and set option has readonly option """ # cache_option is None only when we start to build cache if cache_option is None: if self.impl_is_readonly(): raise ConfigError(_('option description seems to be part of an other ' 'config')) init = True _consistencies = {} cache_option = [] force_store_values = [] _dependencies = [] else: init = False for option in self.impl_getchildren(setting_properties=undefined, dyn=False): cache_option.append(option) if path == '': subpath = option.impl_getname() else: subpath = path + '.' + option.impl_getname() if isinstance(option, OptionDescription): option._set_readonly() option._build_cache(config, subpath, _consistencies, cache_option, force_store_values, _dependencies) else: option._set_readonly() is_multi = option.impl_is_multi() if not option.impl_is_symlinkoption() and 'force_store_value' in option.impl_getproperties(): force_store_values.append((subpath, option)) for func, all_cons_opts, params in option._get_consistencies(): option._valid_consistencies(all_cons_opts[1:], init=False) if func not in ALLOWED_CONST_LIST and is_multi: is_masterslaves = option.impl_is_master_slaves() if not is_masterslaves: raise ConfigError(_('malformed consistency option "{0}" ' 'must be a master/slaves').format( option.impl_getname())) masterslaves = option.impl_get_master_slaves() for opt in all_cons_opts: if func not in ALLOWED_CONST_LIST and is_multi: if not opt.impl_is_master_slaves(): raise ConfigError(_('malformed consistency option "{0}" ' 'must not be a multi for "{1}"').format( option.impl_getname(), opt.impl_getname())) elif masterslaves != opt.impl_get_master_slaves(): raise ConfigError(_('malformed consistency option "{0}" ' 'must be in same master/slaves for "{1}"').format( option.impl_getname(), opt.impl_getname())) _consistencies.setdefault(opt, []).append((func, all_cons_opts, params)) # if context is set to callback, must be reset each time a value change if hasattr(option, '_has_calc_context'): _dependencies.append(option) is_slave = None if is_multi: all_requires = option.impl_getrequires() if all_requires != tuple(): for requires in all_requires: for require in requires: #if option in require is a multi: # * option in require must be a master or a slave # * current option must be a slave (and only a slave) # * option in require and current option must be in same master/slaves for require_opt, values in require[0]: if require_opt.impl_is_multi(): if is_slave is None: is_slave = option.impl_is_master_slaves('slave') if is_slave: masterslaves = option.impl_get_master_slaves() if is_slave and require_opt.impl_is_master_slaves(): if masterslaves != require_opt.impl_get_master_slaves(): raise ValueError(_('malformed requirements option {0} ' 'must be in same master/slaves for {1}').format( require_opt.impl_getname(), option.impl_getname())) else: raise ValueError(_('malformed requirements option "{0}" ' 'must not be a multi for "{1}"').format( require_opt.impl_getname(), option.impl_getname())) if init: if len(cache_option) != len(set(cache_option)): for idx in xrange(1, len(cache_option) + 1): opt = cache_option.pop(0) if opt in cache_option: raise ConflictError(_('duplicate option: {0}').format(opt)) if _consistencies != {}: self._cache_consistencies = {} for opt, cons in _consistencies.items(): if opt() not in cache_option: # pragma: optional cover raise ConfigError(_('consistency with option {0} ' 'which is not in Config').format( opt.impl_getname())) self._cache_consistencies[opt] = tuple(cons) self._cache_force_store_values = force_store_values if _dependencies: self._dependencies = tuple(_dependencies) self._set_readonly() def impl_already_build_caches(self): return getattr(self, '_cache_paths', None) is not None def impl_build_force_store_values(self, config, force_store_values): session = config._impl_values._p_.getsession() value_set = False for subpath, option in self._cache_force_store_values: if option.impl_is_master_slaves('slave'): # problem with index raise ConfigError(_('a slave ({0}) cannot have ' 'force_store_value property').format(subpath)) if option._is_subdyn(): raise ConfigError(_('a dynoption ({0}) cannot have ' 'force_store_value property').format(subpath)) if force_store_values and not config._impl_values._p_.hasvalue(subpath, session): value = config.cfgimpl_get_values().get_cached_value(option, path=subpath, validate=False, trusted_cached_properties=False, validate_properties=True) value_set = True config._impl_values._p_.setvalue(subpath, value, owners.forced, None, session, False) if value_set: config._impl_values._p_.commit() def _build_cache_option(self, _currpath=None, cache_path=None, cache_option=None): if self.impl_is_readonly() or (_currpath is None and getattr(self, '_cache_paths', None) is not None): # cache already set return if _currpath is None: save = True _currpath = [] else: save = False if cache_path is None: cache_path = [] cache_option = [] for option in self.impl_getchildren(setting_properties=undefined, dyn=False): attr = option.impl_getname() path = str('.'.join(_currpath + [attr])) cache_option.append(option) cache_path.append(path) if option.impl_is_optiondescription(): _currpath.append(attr) option._build_cache_option(_currpath, cache_path, cache_option) _currpath.pop() if save: _setattr = object.__setattr__ _setattr(self, '_cache_paths', (tuple(cache_option), tuple(cache_path))) class OptionDescriptionWalk(CacheOptionDescription): __slots__ = ('_children',) def impl_get_options_paths(self, bytype, byname, _subpath, only_first, context, setting_properties): find_results = [] def _rebuild_dynpath(path, suffix, dynopt): found = False spath = path.split('.') for length in xrange(1, len(spath)): subpath = '.'.join(spath[0:length]) subopt = self.impl_get_opt_by_path(subpath) if dynopt == subopt: found = True break if not found: # pragma: no cover raise ConfigError(_('cannot find dynpath')) subpath = subpath + suffix for slength in xrange(length, len(spath)): subpath = subpath + '.' + spath[slength] + suffix return subpath def _filter_by_name(path, option): name = option.impl_getname() if option._is_subdyn(): found = False if byname.startswith(name): subdyn = option._subdyn() for suffix in subdyn._impl_get_suffixes(context, setting_properties): if byname == name + suffix: found = True path = _rebuild_dynpath(path, suffix, subdyn) option = option._impl_to_dyn(name + suffix, path) break if not found: return False else: if not byname == name: return False find_results.append((path, option)) return True def _filter_by_type(path, option): if isinstance(option, bytype): #if byname is not None, check option byname in _filter_by_name #not here if byname is None: if option._is_subdyn(): name = option.impl_getname() for suffix in option._subdyn._impl_get_suffixes(context, setting_properties): spath = _rebuild_dynpath(path, suffix, option._subdyn) find_results.append((spath, option._impl_to_dyn( name + suffix, spath))) else: find_results.append((path, option)) return True return False def _filter(path, option): if bytype is not None: retval = _filter_by_type(path, option) if byname is None: return retval if byname is not None: return _filter_by_name(path, option) opts, paths = self._cache_paths for index, path in enumerate(paths): option = opts[index] if option.impl_is_optiondescription(): continue if _subpath is not None and not path.startswith(_subpath + '.'): continue if bytype == byname is None: if option._is_subdyn(): name = option.impl_getname() for suffix in option._subdyn._impl_get_suffixes(context, setting_properties): spath = _rebuild_dynpath(path, suffix, option._subdyn) find_results.append((spath, option._impl_to_dyn(name + suffix, spath))) else: find_results.append((path, option)) else: if _filter(path, option) is False: continue if only_first: return find_results return find_results def _getattr(self, name, setting_properties, suffix=undefined, context=undefined, dyn=True): error = False if suffix is not undefined: if undefined in [suffix, context]: # pragma: no 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)] return self._impl_get_dynchild(child, suffix) else: error = True else: if name in self._children[0]: child = self._children[1][self._children[0].index(name)] if dyn and child.impl_is_dynoptiondescription(): error = True else: return child else: child = self._impl_search_dynchild(name, context=context, setting_properties=setting_properties) if child != []: return child error = True if error: raise AttributeError(_('unknown Option {0} ' 'in OptionDescription {1}' '').format(name, self.impl_getname())) def impl_get_opt_by_path(self, path): if getattr(self, '_cache_paths', None) is None: raise ConfigError(_('use impl_get_opt_by_path only with root OptionDescription')) if path not in self._cache_paths[1]: raise AttributeError(_('no option for path {0}').format(path)) return self._cache_paths[0][self._cache_paths[1].index(path)] def impl_get_path_by_opt(self, opt): if getattr(self, '_cache_paths', None) is None: raise ConfigError(_('use impl_get_path_by_opt only with root OptionDescription')) if opt not in self._cache_paths[0]: raise AttributeError(_('no option {0} found').format(opt)) return self._cache_paths[1][self._cache_paths[0].index(opt)] def impl_getchildren(self, setting_properties, dyn=True, context=undefined): for child in self._impl_st_getchildren(): cname = child.impl_getname() if dyn and child.impl_is_dynoptiondescription(): for value in child._impl_get_suffixes(context, setting_properties): yield SynDynOptionDescription(child, cname + value, value) else: yield child def _impl_st_getchildren(self, only_dyn=False): for child in self._children[1]: if only_dyn is False or child.impl_is_dynoptiondescription(): yield child def _impl_search_dynchild(self, name, context, setting_properties): ret = [] for child in self._impl_st_getchildren(only_dyn=True): cname = child.impl_getname() if name.startswith(cname): for value in child._impl_get_suffixes(context, setting_properties): if name == cname + value: return SynDynOptionDescription(child, name, value) return ret def __getattr__(self, name, context=undefined, setting_properties=undefined): if name.startswith('_'): return object.__getattribute__(self, name) if '.' in name: path = name.split('.')[0] subpath = '.'.join(name.split('.')[1:]) return self.__getattr__(path, context=context).__getattr__(subpath, context=context, setting_properties=setting_properties) return self._getattr(name, context=context, setting_properties=setting_properties) def _impl_get_dynchild(self, child, suffix): name = child.impl_getname() + suffix path = self.impl_getname() + suffix + '.' + name if isinstance(child, OptionDescription): return SynDynOptionDescription(child, name, suffix) else: return child._impl_to_dyn(name, path) class OptionDescription(OptionDescriptionWalk): """Config's schema (organisation, group) and container of Options The `OptionsDescription` objects lives in the `tiramisu.config.Config`. """ __slots__ = ('_group_type',) def __init__(self, name, doc, children, requires=None, properties=None): """ :param children: a list of options (including optiondescriptions) """ if not isinstance(children, list): raise ValueError(_('children in optiondescription "{}"must be a liste').format(name)) super(OptionDescription, self).__init__(name, doc=doc, requires=requires, properties=properties) child_names = [] dynopt_names = [] for child in children: name = child.impl_getname() child_names.append(name) if isinstance(child, DynOptionDescription): dynopt_names.append(name) #better performance like this valid_child = copy(child_names) valid_child.sort() old = None for child in valid_child: if child == old: # pragma: optional cover raise ConflictError(_('duplicate option name: ' '"{0}"').format(child)) if dynopt_names: for dynopt in dynopt_names: if child != dynopt and child.startswith(dynopt): raise ConflictError(_('the option\'s name "{}" start as ' 'the dynoptiondescription\'s name "{}"').format(child, dynopt)) old = child _setattr = object.__setattr__ _setattr(self, '_children', (tuple(child_names), tuple(children))) _setattr(self, '_cache_consistencies', None) # the group_type is useful for filtering OptionDescriptions in a config _setattr(self, '_group_type', groups.default) def impl_getdoc(self): return self.impl_get_information('doc') def impl_validate(self, *args, **kwargs): """usefull for OptionDescription""" pass # ____________________________________________________________ def impl_set_group_type(self, group_type, setting_properties): """sets a given group object to an OptionDescription :param group_type: an instance of `GroupType` or `MasterGroupType` that lives in `setting.groups` """ if self._group_type != groups.default: # pragma: optional cover raise TypeError(_('cannot change group_type if already set ' '(old {0}, new {1})').format(self._group_type, group_type)) if isinstance(group_type, groups.GroupType): self._group_type = group_type if isinstance(group_type, groups.MasterGroupType): raise Exception('please use MasterSlaves object instead of OptionDescription') children = self.impl_getchildren(setting_properties) for child in children: if child.impl_is_symlinkoption(): # pragma: optional cover raise ValueError(_("master group {0} shall not have " "a symlinkoption").format(self.impl_getname())) if not isinstance(child, Option): # pragma: optional cover raise ValueError(_("master group {0} shall not have " "a subgroup").format(self.impl_getname())) if not child.impl_is_multi(): # pragma: optional cover raise ValueError(_("not allowed option {0} " "in group {1}" ": this option is not a multi" "").format(child.impl_getname(), self.impl_getname())) #length of master change slaves length MasterSlaves(self.impl_getname(), children) else: # pragma: optional cover raise ValueError(_('group_type: {0}' ' not allowed').format(group_type)) def impl_get_group_type(self): return self._group_type def impl_validate_value(self, option, value, context): pass 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) # check children + set relation to this dynoptiondescription for child in children: if isinstance(child, OptionDescription): if child.impl_get_group_type() != groups.master: raise ConfigError(_('cannot set optiondescription in a ' 'dynoptiondescription')) for chld in child.impl_getchildren(setting_properties=undefined): chld._impl_setsubdyn(self) if child.impl_is_symlinkoption(): raise ConfigError(_('cannot set symlinkoption in a ' 'dynoptiondescription')) child._impl_setsubdyn(self) # add callback self.impl_set_callback(callback, callback_params) def _validate_callback(self, callback, callback_params): if callback is None: raise ConfigError(_('callback is mandatory for dynoptiondescription')) def _impl_get_suffixes(self, context, setting_properties): callback, callback_params = self.impl_get_callback() values = carry_out_calculation(self, context=context, callback=callback, callback_params=callback_params, setting_properties=setting_properties) if len(values) > len(set(values)): raise ConfigError(_('DynOptionDescription callback return not unique value')) for val in values: if not isinstance(val, str) or re.match(NAME_REGEXP, val) is None: raise ValueError(_("invalid suffix: {0} for option").format(val)) return values class SynDynOptionDescription(object): __slots__ = ('_opt', '_name', '_suffix') def __init__(self, opt, name, suffix): self._opt = opt self._name = name self._suffix = suffix def __getattr__(self, name, setting_properties=undefined, context=undefined): if name in dir(self._opt): return getattr(self._opt, name) return self._opt._getattr(name, setting_properties, suffix=self._suffix, context=context) def impl_getname(self): return self._name def impl_getchildren(self, setting_properties, dyn=True, context=undefined): children = [] for child in self._opt.impl_getchildren(setting_properties): yield(self._opt._impl_get_dynchild(child, self._suffix)) def impl_getpath(self, context): path = self.impl_getopt().impl_getpath(context).split('.') path[-1] += self._suffix path.append(self._name) return '.'.join(path) def impl_getopt(self): return self._opt class MasterSlaves(OptionDescription): __slots__ = ('master', 'slaves') def __init__(self, name, doc, children, requires=None, properties=None): super(MasterSlaves, self).__init__(name, doc, children, requires=requires, properties=properties) self._group_type = groups.master slaves = [] master = children[0] if not children: raise ValueError(_('children is mandatory in masterslaves "{}"').format(name)) for child in children[1:]: if child.impl_getdefault() != []: raise ValueError(_("not allowed default value for option {0} " "in master/slave object {1}").format(child.impl_getname(), name)) slaves.append(child) child._add_dependency(self) for idx, child in enumerate(children): if child.impl_is_symlinkoption(): # pragma: optional cover raise ValueError(_("master group {0} shall not have " "a symlinkoption").format(self.impl_getname())) if not isinstance(child, Option): # pragma: optional cover raise ValueError(_("master group {0} shall not have " "a subgroup").format(self.impl_getname())) if not child.impl_is_multi(): # pragma: optional cover raise ValueError(_("not allowed option {0} " "in group {1}" ": this option is not a multi" "").format(child.impl_getname(), self.impl_getname())) # no empty property for save if idx != 0: properties = list(child._properties) properties.remove('empty') child._properties = tuple(properties) callback, callback_params = master.impl_get_callback() if callback is not None and callback_params != {}: for callbacks in callback_params.values(): for callbk in callbacks: if isinstance(callbk, tuple): if callbk[0] in slaves: raise ValueError(_("callback of master's option shall " "not refered a slave's ones")) #everything is ok, store references for child in children: child._master_slaves = weakref.ref(self) master._add_dependency(self) def is_master(self, opt): master = self._children[0][0] return opt.impl_getname() == master or (opt.impl_is_dynsymlinkoption() and opt._opt.impl_getname() == master) def getmaster(self, opt): master = self._children[1][0] if opt is not None and opt.impl_is_dynsymlinkoption(): suffix = opt.impl_getsuffix() name = master.impl_getname() + suffix base_path = opt._dyn.split('.')[0] + '.' path = base_path + name master = master._impl_to_dyn(name, path) return master def getslaves(self, opt): if opt.impl_is_dynsymlinkoption(): for slave in self._children[1][1:]: suffix = opt.impl_getsuffix() name = slave.impl_getname() + suffix base_path = opt._dyn.split('.')[0] + '.' path = base_path + name yield slave._impl_to_dyn(name, path) else: for slave in self._children[1][1:]: yield slave def in_same_group(self, opt): if opt.impl_is_dynsymlinkoption(): c_opt = opt._opt else: c_opt = opt return child in self._children[1] def reset(self, opt, values, setting_properties, _commit=True, force_permissive=False): for slave in self.getslaves(opt): slave_path = slave.impl_getpath(values._getcontext()) values.reset(slave, slave_path, setting_properties, validate=False, _commit=_commit, force_permissive=force_permissive) def pop(self, opt, path, values, index, setting_properties, force_permissive=False): for slave in self.getslaves(opt): slave_path = slave.impl_getpath(values._getcontext()) slavelen = values._p_.get_max_length(slave_path) if not values.is_default_owner(slave, slave_path, setting_properties, validate_meta=False, index=index, force_permissive=force_permissive): #FIXME # just for raise if needed #multi = values.get_cached_value(slave, # validate=False, # validate_properties=False, # ) #if isinstance(multi, Exception): # raise multi if slavelen > index: values._p_.resetvalue_index(slave_path, index) if slavelen > index + 1: for idx in xrange(index + 1, slavelen): values._p_.reduce_index(slave_path, idx) def getitem(self, values, opt, path, validate, force_permissive, trusted_cached_properties, validate_properties, setting_properties=undefined, self_properties=undefined, index=None, check_frozen=False): if self.is_master(opt): return self._getmaster(values, opt, path, validate, force_permissive, validate_properties, self_properties, index, setting_properties, check_frozen) else: return self._getslave(values, opt, path, validate, force_permissive, trusted_cached_properties, validate_properties, setting_properties, self_properties, index, check_frozen) def _getmaster(self, values, opt, path, validate, force_permissive, validate_properties, self_properties, index, setting_properties, check_frozen): return values.get_cached_value(opt, path=path, validate=validate, force_permissive=force_permissive, self_properties=self_properties, index=index, setting_properties=setting_properties) def _getslave(self, values, opt, path, validate, force_permissive, trusted_cached_properties, validate_properties, setting_properties, self_properties, index, check_frozen): """ if master has length 0: return [] if master has length bigger than 0: if default owner: if has callback: if return a list: list same length as master: return list list is smaller than master: return list + None list is greater than master: raise SlaveError if has default value: list same length as master: return list list is smaller than master: return list + None list is greater than master: raise SlaveError if has default_multi value: return default_multi * master's length if has value: list same length as master: return list list is smaller than master: return list + None list is greater than master: raise SlaveError """ master = self.getmaster(opt) context = values._getcontext() masterp = master.impl_getpath(context) try: mastervalue = values.get_cached_value(master, path=masterp, validate=validate, force_permissive=force_permissive, validate_properties=validate_properties, self_properties=self_properties, from_masterslave=True, setting_properties=setting_properties, check_frozen=check_frozen) except PropertiesOptionError as mastervalue: mastervalue.set_orig_opt(opt) raise mastervalue masterlen = len(mastervalue) #self._master_is_meta = values._is_meta(master, masterp, force_permissive=force_permissive) multi = list() # values._get_multi(opt, path) if validate_properties: props = context.cfgimpl_get_settings().validate_properties(opt, False, check_frozen, value=multi, path=path, force_permissive=force_permissive, setting_properties=setting_properties) if props: return props #FIXME shouldn't have index!!! if index is None: indexes = xrange(0, masterlen) else: indexes = [index] for idx in indexes: try: value = values.get_cached_value(opt, path, validate, force_permissive, trusted_cached_properties, validate_properties, index=idx, # not self_properties, # depends to index #self_properties=self_properties, setting_properties=setting_properties, from_masterslave=True, check_frozen=check_frozen) except PropertiesOptionError as perr: err = perr if index is None: multi.append(err) else: multi = err if index is None: multi.append(value) else: multi = value return multi def validate(self, values, opt, index, path, setitem): if self.is_master(opt): #for regen slave path base_path = '.'.join(path.split('.')[:-1]) + '.' for slave in self.getslaves(opt): slave_path = base_path + slave.impl_getname() slavelen = values._p_.get_max_length(slave_path) self.validate_slave_length(index, slavelen, slave.impl_getname(), opt) else: val_len = self.get_length(values) if isinstance(val_len, Exception): return val_len self.validate_slave_length(val_len, index, opt.impl_getname(), opt, setitem=setitem) def get_length(self, values, validate=True, force_permissive=False, master=None, masterp=None, setting_properties=undefined): """get master len with slave option""" if master is None: master = self.getmaster(None) if masterp is None: masterp = master.impl_getpath(values._getcontext()) value = self._getmaster(values, master, masterp, validate, force_permissive, validate, undefined, None, setting_properties, False) if isinstance(value, Exception): return value return len(value) def validate_slave_length(self, masterlen, valuelen, name, opt, setitem=False): if valuelen > masterlen or (valuelen < masterlen and setitem): if debug: # pragma: no cover log.debug('validate_slave_length: masterlen: {0}, valuelen: {1}, ' 'setitem: {2}'.format(masterlen, valuelen, setitem)) if not opt.impl_is_master_slaves('master'): opt = self.getmaster(opt) raise SlaveError(_("invalid len for the slave: {0}" " which has {1} as master").format( name, opt.impl_getname())) def reset_cache(self, opt, path, obj, type_, resetted_opts): context = obj._getcontext() #FIXME pb avec dyn, devrait etre une option mopt = self.getmaster(None) mpath = mopt.impl_getpath(context) mopt.reset_cache(mopt, mpath, obj, type_, resetted_opts) for slave in self.getslaves(mopt): spath = slave.impl_getpath(context) slave.reset_cache(slave, spath, obj, type_, resetted_opts) def _getattr(self, name, setting_properties, suffix=undefined, context=undefined, dyn=True): return super(MasterSlaves, self)._getattr(name, setting_properties, suffix, context, dyn) def impl_validate(self, context, force_permissive, setting_properties, masterlen=None, slavelen=None, opt=None, setitem=False): values = context.cfgimpl_get_values() if masterlen is None: master = self.getmaster(opt) masterp = master.impl_getpath(context) mastervalue = values.get_cached_value(master, path=masterp, force_permissive=force_permissive, setting_properties=setting_properties) if isinstance(mastervalue, Exception): return mastervalue masterlen = len(mastervalue) else: master = opt if slavelen is not None: self.validate_slave_length(masterlen, slavelen, opt.impl_getname(), master, setitem=setitem) else: for slave in self.getslaves(master): slave_path = slave.impl_getpath(context) slavelen = values._p_.get_max_length(slave_path) self.validate_slave_length(masterlen, slavelen, slave.impl_getname(), master) def impl_validate_value(self, option, value, context): if option.impl_is_master_slaves('master') and isinstance(value, list): if len(value) < context._impl_length: return ValueError(_('cannot reduce length of master "{}"' '').format(option.impl_get_display_name()))