# -*- coding: utf-8 -*- "takes care of the option's values and multi values" # Copyright (C) 2013-2014 Team tiramisu (see AUTHORS for all contributors) # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # 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 . # ____________________________________________________________ from time import time import sys import weakref from .error import ConfigError, SlaveError, PropertiesOptionError from .setting import owners, expires_time, undefined from .autolib import carry_out_calculation from .i18n import _ from .option import SymLinkOption, DynSymLinkOption, Option class Values(object): """The `Config`'s root is indeed in charge of the `Option()`'s values, but the values are physicaly located here, in `Values`, wich is also responsible of a caching utility. """ __slots__ = ('context', '_p_', '__weakref__') def __init__(self, context, storage): """ Initializes the values's dict. :param context: the context is the home config's values """ self.context = weakref.ref(context) # the storage type is dictionary or sqlite3 self._p_ = storage def _getcontext(self): """context could be None, we need to test it context is None only if all reference to `Config` object is deleted (for example we delete a `Config` and we manipulate a reference to old `SubConfig`, `Values`, `Multi` or `Settings`) """ context = self.context() if context is None: raise ConfigError(_('the context does not exist anymore')) return context def _get_multi(self, opt, path): return Multi([], self.context, opt, path) def _getdefaultvalue(self, opt, path, with_meta, index, submulti_index, validate): # if value has callback and is not set if opt.impl_has_callback(): callback, callback_params = opt.impl_get_callback() value = carry_out_calculation(opt, context=self._getcontext(), callback=callback, callback_params=callback_params, index=index, validate=validate) if isinstance(value, list) and index is not None: #if return a list and index is set, return value only if #it's a submulti without submulti_index and without list of list if opt.impl_is_submulti() and submulti_index is undefined and \ (len(value) == 0 or not isinstance(value[0], list)): return value if not opt.impl_is_submulti() and len(value) > index: return value[index] else: return value if with_meta: meta = self._getcontext().cfgimpl_get_meta() if meta is not None: value = meta.cfgimpl_get_values( )._get_cached_value(opt, path, index=index, submulti_index=submulti_index, from_masterslave=True) if isinstance(value, Exception): if not isinstance(value, PropertiesOptionError): # pragma: no cover raise value else: if isinstance(value, Multi): if index is not None: value = value[index] if isinstance(value, SubMulti): if submulti_index is not undefined: value = value[submulti_index] else: value = list(value) else: new_value = [] for val in value: if isinstance(val, SubMulti): val = list(val) new_value.append(val) value = new_value del new_value return value # now try to get default value value = opt.impl_getdefault() if opt.impl_is_multi() and index is not None: if value == []: value = opt.impl_getdefault_multi() else: if len(value) > index: value = value[index] else: value = opt.impl_getdefault_multi() return value def _getvalue(self, opt, path, self_properties, index, submulti_index, with_meta, masterlen, session, validate): """actually retrieves the value :param opt: the `option.Option()` object :returns: the option's value (or the default value if not set) """ force_default = 'frozen' in self_properties and \ 'force_default_on_freeze' in self_properties # not default value is_default = self._is_default_owner(opt, path, session, validate_properties=False, validate_meta=False, self_properties=self_properties, index=index) if not is_default and not force_default: if opt.impl_is_master_slaves('slave'): return self._p_.getvalue(path, session, index) else: value = self._p_.getvalue(path, session) if index is not None: if len(value) > index: return value[index] #value is smaller than expected #so return default value else: return value return self._getdefaultvalue(opt, path, with_meta, index, submulti_index, validate) def get_modified_values(self): return self._p_.get_modified_values() def __contains__(self, opt): """ implements the 'in' keyword syntax in order provide a pythonic way to kow if an option have a value :param opt: the `option.Option()` object """ path = opt.impl_getpath(self._getcontext()) return self._contains(path) def _contains(self, path, session=None): if session is None: session = self._p_.getsession() return self._p_.hasvalue(path, session) def __delitem__(self, opt): """overrides the builtins `del()` instructions""" self.reset(opt) def reset(self, opt, path=None, validate=True, _setting_properties=None): context = self._getcontext() setting = context.cfgimpl_get_settings() if path is None: path = opt.impl_getpath(context) if _setting_properties is None: _setting_properties = setting._getproperties(read_write=False) session = self._p_.getsession() hasvalue = self._contains(path, session) if validate and hasvalue and 'validator' in _setting_properties: session = context.cfgimpl_get_values()._p_.getsession() fake_context = context._gen_fake_values(session) fake_value = fake_context.cfgimpl_get_values() fake_value.reset(opt, path, validate=False) ret = fake_value._get_cached_value(opt, path, setting_properties=_setting_properties, check_frozen=True) if isinstance(ret, Exception): raise ret if opt.impl_is_master_slaves('master'): opt.impl_get_master_slaves().reset(opt, self, _setting_properties) if hasvalue: if 'force_store_value' in setting._getproperties(opt=opt, path=path, setting_properties=_setting_properties, read_write=False, apply_requires=False): value = self._getdefaultvalue(opt, path, True, undefined, undefined, validate) if isinstance(value, Exception): # pragma: no cover raise value self._setvalue(opt, path, value, force_owner=owners.forced) else: self._p_.resetvalue(path, session) context.cfgimpl_reset_cache() def _isempty(self, opt, value, force_allow_empty_list=False, index=None): "convenience method to know if an option is empty" if value is undefined: return False else: empty = opt._empty if index in [None, undefined] and opt.impl_is_multi(): if force_allow_empty_list: allow_empty_list = True else: allow_empty_list = opt.impl_allow_empty_list() if allow_empty_list is undefined: if opt.impl_is_master_slaves('slave'): allow_empty_list = True else: allow_empty_list = False isempty = value is None or (not allow_empty_list and value == []) or \ None in value or empty in value else: isempty = value is None or value == empty return isempty def __getitem__(self, opt): "enables us to use the pythonic dictionary-like access to values" return self._get_cached_value(opt) def getitem(self, opt, validate=True, force_permissive=False): """ """ return self._get_cached_value(opt, validate=validate, force_permissive=force_permissive) def _get_cached_value(self, opt, path=None, validate=True, force_permissive=False, trusted_cached_properties=True, validate_properties=True, setting_properties=undefined, self_properties=undefined, index=None, submulti_index=undefined, from_masterslave=False, with_meta=True, masterlen=undefined, check_frozen=False, session=None, display_warnings=True): context = self._getcontext() settings = context.cfgimpl_get_settings() if path is None: path = opt.impl_getpath(context) ntime = None if setting_properties is undefined: setting_properties = settings._getproperties(read_write=False) if self_properties is undefined: self_properties = settings._getproperties(opt, path, read_write=False, setting_properties=setting_properties, index=index) if 'cache' in setting_properties and self._p_.hascache(path, index): if 'expire' in setting_properties: ntime = int(time()) is_cached, value = self._p_.getcache(path, ntime, index) if is_cached: if opt.impl_is_multi() and not isinstance(value, Multi) and index is None: value = Multi(value, self.context, opt, path) if not trusted_cached_properties: # revalidate properties (because of not default properties) props = settings.validate_properties(opt, False, False, value=value, path=path, force_permissive=force_permissive, setting_properties=setting_properties, self_properties=self_properties, index=index) if props: return props return value if session is None: session = self._p_.getsession() if not from_masterslave and opt.impl_is_master_slaves(): val = opt.impl_get_master_slaves().getitem(self, opt, path, validate, force_permissive, trusted_cached_properties, validate_properties, session, setting_properties=setting_properties, index=index, self_properties=self_properties) else: val = self._get_validated_value(opt, path, validate, force_permissive, validate_properties, setting_properties, self_properties, with_meta=with_meta, masterlen=masterlen, index=index, submulti_index=submulti_index, check_frozen=check_frozen, session=session, display_warnings=display_warnings) if isinstance(val, Exception): return val # cache doesn't work with SubMulti yet if not isinstance(val, SubMulti) and 'cache' in setting_properties and \ validate and validate_properties and force_permissive is False \ and trusted_cached_properties is True: if 'expire' in setting_properties: if ntime is None: ntime = int(time()) ntime = ntime + expires_time self._p_.setcache(path, val, ntime, index) return val def _get_validated_value(self, opt, path, validate, force_permissive, validate_properties, setting_properties, self_properties, index=None, submulti_index=undefined, with_meta=True, masterlen=undefined, check_frozen=False, session=None, display_warnings=True): """same has getitem but don't touch the cache index is None for slave value, if value returned is not a list, just return [] """ context = self._getcontext() setting = context.cfgimpl_get_settings() config_error = None if session is None: session = self._p_.getsession() value = self._getvalue(opt, path, self_properties, index, submulti_index, with_meta, masterlen, session, validate) if isinstance(value, Exception): value_error = True if isinstance(value, ConfigError): # For calculating properties, we need value (ie for mandatory # value). # If value is calculating with a PropertiesOptionError's option # _getvalue raise a ConfigError. # We can not raise ConfigError if this option should raise # PropertiesOptionError too. So we get config_error and raise # ConfigError if properties did not raise. config_error = value # value is not set, for 'undefined' (cannot set None because of # mandatory property) value = undefined else: # pragma: no cover raise value else: value_error = False if opt.impl_is_multi(): if index is None: value = Multi(value, self.context, opt, path) elif opt.impl_is_submulti() and submulti_index is undefined: value = SubMulti(value, self.context, opt, path, index) if validate: if submulti_index is undefined: force_submulti_index = None else: force_submulti_index = submulti_index err = opt.impl_validate(value, context, 'validator' in setting_properties, force_index=index, force_submulti_index=force_submulti_index, display_error=True, display_warnings=False) if err: config_error = err value = None if validate_properties: if config_error is not None: # should not raise PropertiesOptionError if option is # mandatory val_props = undefined else: val_props = value props = setting.validate_properties(opt, False, check_frozen, value=val_props, path=path, force_permissive=force_permissive, setting_properties=setting_properties, self_properties=self_properties, index=index) if props: return props if not value_error and validate and display_warnings: opt.impl_validate(value, context, 'validator' in setting_properties, force_index=index, force_submulti_index=force_submulti_index, display_error=False, display_warnings=display_warnings) if config_error is not None: return config_error return value def __setitem__(self, opt, value): raise ConfigError(_('you should only set value with config')) def setitem(self, opt, value, path, force_permissive=False, check_frozen=True, not_raises=False): # check_frozen is, for example, used with "force_store_value" # user didn't change value, so not write # valid opt context = self._getcontext() setting_properties = context.cfgimpl_get_settings()._getproperties(read_write=False) if 'validator' in setting_properties: session = context.cfgimpl_get_values()._p_.getsession() fake_context = context._gen_fake_values(session) fake_values = fake_context.cfgimpl_get_values() fake_values._setvalue(opt, path, value) props = fake_values.validate(opt, value, path, check_frozen=check_frozen, force_permissive=force_permissive, setting_properties=setting_properties, session=session, not_raises=not_raises) if props and not_raises: return err = opt.impl_validate(value, fake_context, display_warnings=False) if err: raise err opt.impl_validate(value, fake_context, display_error=False) self._setvalue(opt, path, value) def _setvalue(self, opt, path, value, force_owner=undefined, index=None): context = self._getcontext() context.cfgimpl_reset_cache() if force_owner is undefined: owner = context.cfgimpl_get_settings().getowner() else: owner = force_owner # in storage, value must not be a multi if isinstance(value, Multi): value = list(value) if opt.impl_is_submulti(): for idx, val in enumerate(value): if isinstance(val, SubMulti): value[idx] = list(val) session = self._p_.getsession() #FIXME pourquoi là et pas dans masterslaves ?? if opt.impl_is_master_slaves('slave'): if index is not None: self._p_.setvalue(path, value[index], owner, index, session) else: self._p_.resetvalue(path, session) for idx, val in enumerate(value): self._p_.setvalue(path, val, owner, idx, session) else: self._p_.setvalue(path, value, owner, None, session) del(session) def validate(self, opt, value, path, check_frozen=True, force_permissive=False, setting_properties=undefined, valid_masterslave=True, not_raises=False, session=None): if valid_masterslave and opt.impl_is_master_slaves(): if session is None: session = self._p_.getsession() val = opt.impl_get_master_slaves().validate(self, opt, value, path, session) if isinstance(val, Exception): return val props = self._getcontext().cfgimpl_get_settings().validate_properties(opt, False, check_frozen, value=value, path=path, force_permissive=force_permissive, setting_properties=setting_properties) if props: if not_raises: return props raise props def _is_meta(self, opt, path, session): context = self._getcontext() setting = context.cfgimpl_get_settings() self_properties = setting._getproperties(opt, path, read_write=False) if 'frozen' in self_properties and 'force_default_on_freeze' in self_properties: return False if self._p_.getowner(path, owners.default, session, only_default=True) is not owners.default: return False if context.cfgimpl_get_meta() is not None: return True return False def getowner(self, opt, index=None, force_permissive=False, session=None): """ retrieves the option's owner :param opt: the `option.Option` object :param force_permissive: behaves as if the permissive property was present :returns: a `setting.owners.Owner` object """ if isinstance(opt, SymLinkOption) and \ not isinstance(opt, DynSymLinkOption): opt = opt._impl_getopt() path = opt.impl_getpath(self._getcontext()) return self._getowner(opt, path, session, index=index, force_permissive=force_permissive) def _getowner(self, opt, path, session, validate_properties=True, force_permissive=False, validate_meta=undefined, self_properties=undefined, only_default=False, index=None): """get owner of an option """ if session is None: session = self._p_.getsession() if not isinstance(opt, Option) and not isinstance(opt, DynSymLinkOption): raise ConfigError(_('owner only avalaible for an option')) context = self._getcontext() if self_properties is undefined: self_properties = context.cfgimpl_get_settings()._getproperties( opt, path, read_write=False) if 'frozen' in self_properties and 'force_default_on_freeze' in self_properties: return owners.default if validate_properties: value = self._get_cached_value(opt, path, True, force_permissive, None, True, self_properties=self_properties, session=session) if isinstance(value, Exception): raise value owner = self._p_.getowner(path, owners.default, session, only_default=only_default, index=index) if validate_meta is undefined: if opt.impl_is_master_slaves('slave'): master = opt.impl_get_master_slaves().getmaster(opt) masterp = master.impl_getpath(context) validate_meta = self._is_meta(opt, masterp, session) else: validate_meta = True if validate_meta: meta = context.cfgimpl_get_meta() if owner is owners.default and meta is not None: owner = meta.cfgimpl_get_values()._getowner(opt, path, session, validate_properties=validate_properties, force_permissive=force_permissive, self_properties=self_properties, only_default=only_default, index=index) return owner def setowner(self, opt, owner, index=None): """ sets a owner to an option :param opt: the `option.Option` object :param owner: a valid owner, that is a `setting.owners.Owner` object """ if not isinstance(owner, owners.Owner): raise TypeError(_("invalid generic owner {0}").format(str(owner))) path = opt.impl_getpath(self._getcontext()) session = self._p_.getsession() if not self._p_.hasvalue(path, session): raise ConfigError(_('no value for {0} cannot change owner to {1}' '').format(path, owner)) props = self._getcontext().cfgimpl_get_settings().validate_properties(opt, False, True, path, index=index) if props: raise props self._p_.setowner(path, owner, session, index=index) def is_default_owner(self, opt, validate_properties=True, validate_meta=True, index=None, force_permissive=False): """ :param config: *must* be only the **parent** config (not the toplevel config) :return: boolean """ path = opt.impl_getpath(self._getcontext()) return self._is_default_owner(opt, path, session=None, validate_properties=validate_properties, validate_meta=validate_meta, index=index, force_permissive=force_permissive) def _is_default_owner(self, opt, path, session, validate_properties=True, validate_meta=True, self_properties=undefined, index=None, force_permissive=False): if not opt.impl_is_master_slaves('slave'): index = None d = self._getowner(opt, path, session, validate_properties=validate_properties, validate_meta=validate_meta, self_properties=self_properties, only_default=True, index=index, force_permissive=force_permissive) return d == owners.default def reset_cache(self, only_expired): """ clears the cache if necessary """ if only_expired: self._p_.reset_expired_cache(int(time())) else: self._p_.reset_all_cache() # information def set_information(self, key, value): """updates the information's attribute :param key: information's key (ex: "help", "doc" :param value: information's value (ex: "the help string") """ self._p_.set_information(key, value) def get_information(self, key, default=undefined): """retrieves one information's item :param key: the item string (ex: "help") """ return self._p_.get_information(key, default) def del_information(self, key, raises=True): self._p_.del_information(key, raises) def mandatory_warnings(self, force_permissive=True): """convenience function to trace Options that are mandatory and where no value has been set :returns: generator of mandatory Option's path """ context = self._getcontext() settings = context.cfgimpl_get_settings() setting_properties = context.cfgimpl_get_settings()._getproperties() setting_properties.update(['mandatory', 'empty']) def _is_properties_option(err, path): if not isinstance(err, Exception): pass elif isinstance(err, PropertiesOptionError): if err.proptype == ['mandatory']: return path elif isinstance(err, ConfigError): #assume that uncalculated value is an empty value return path else: raise err def _mandatory_warnings(description, currpath=None): if currpath is None: currpath = [] for opt in description._impl_getchildren(context=context): name = opt.impl_getname() path = '.'.join(currpath + [name]) if opt.impl_is_optiondescription(): if not settings.validate_properties(opt, True, False, path=path, force_permissive=True, setting_properties=setting_properties): for path in _mandatory_warnings(opt, currpath + [name]): yield path else: if isinstance(opt, SymLinkOption) and \ not isinstance(opt, DynSymLinkOption): continue self_properties = settings._getproperties(opt, path, read_write=False, setting_properties=setting_properties) if 'mandatory' in self_properties or 'empty' in self_properties: err = self._get_cached_value(opt, path=path, trusted_cached_properties=False, force_permissive=True, setting_properties=setting_properties, self_properties=self_properties, validate=True, display_warnings=False) if opt.impl_is_master_slaves('slave') and isinstance(err, list): for val in err: ret = _is_properties_option(val, path) if ret is not None: yield ret break else: ret = _is_properties_option(err, path) if ret is not None: yield ret descr = self._getcontext().cfgimpl_get_description() for path in _mandatory_warnings(descr): yield path def force_cache(self): """parse all option to force data in cache """ context = self.context() if not 'cache' in context.cfgimpl_get_settings(): raise ConfigError(_('can force cache only if cache ' 'is actived in config')) #remove all cached properties and value to update "expired" time context.cfgimpl_reset_cache() for path in context.cfgimpl_get_description().impl_getpaths( include_groups=True): err = context.getattr(path, returns_raise=True) if isinstance(err, Exception) and not isinstance(err, PropertiesOptionError): # pragma: no cover raise err def __getstate__(self): return {'_p_': self._p_} def _impl_setstate(self, storage): self._p_._storage = storage def __setstate__(self, states): self._p_ = states['_p_'] # ____________________________________________________________ # multi types class Multi(list): """multi options values container that support item notation for the values of multi options""" __slots__ = ('opt', 'path', 'context', '__weakref__') def __init__(self, value, context, opt, path): """ :param value: the Multi wraps a list value :param context: the home config that has the values :param opt: the option object that have this Multi value :param path: path of the option """ if value is None: value = [] if not opt.impl_is_submulti() and isinstance(value, Multi): raise ValueError(_('{0} is already a Multi ').format( opt.impl_getname())) self.opt = opt self.path = path if not isinstance(context, weakref.ReferenceType): raise ValueError('context must be a Weakref') self.context = context if not isinstance(value, list): if not '_index' in self.__slots__ and opt.impl_is_submulti(): value = [[value]] else: value = [value] elif value != [] and not '_index' in self.__slots__ and \ opt.impl_is_submulti() and not isinstance(value[0], list): value = [value] super(Multi, self).__init__(value) if opt.impl_is_submulti(): if not '_index' in self.__slots__: for idx, val in enumerate(self): if not isinstance(val, SubMulti): super(Multi, self).__setitem__(idx, SubMulti(val, context, opt, path, idx)) self[idx].submulti = weakref.ref(self) def _getcontext(self): """context could be None, we need to test it context is None only if all reference to `Config` object is deleted (for example we delete a `Config` and we manipulate a reference to old `SubConfig`, `Values`, `Multi` or `Settings`) """ context = self.context() if context is None: raise ConfigError(_('the context does not exist anymore')) return context def __setitem__(self, index, value): self._setitem(index, value) def _setitem(self, index, value, validate=True): context = self._getcontext() setting = context.cfgimpl_get_settings() setting_properties = setting._getproperties(read_write=False) if index < 0: index = self.__len__() + index if 'validator' in setting_properties and validate: session = context.cfgimpl_get_values()._p_.getsession() fake_context = context._gen_fake_values(session) fake_multi = fake_context.cfgimpl_get_values()._get_cached_value( self.opt, path=self.path, validate=False) fake_multi._setitem(index, value, validate=False) self._validate(value, fake_context, index, True) #assume not checking mandatory property super(Multi, self).__setitem__(index, value) self._store(index=index) #def __repr__(self, *args, **kwargs): # return super(Multi, self).__repr__(*args, **kwargs) def __getitem__(self, index): value = super(Multi, self).__getitem__(index) if isinstance(value, PropertiesOptionError): raise value return value def __delitem__(self, index): return self.pop(index) def _getdefaultvalue(self, index): values = self._getcontext().cfgimpl_get_values() value = values._getdefaultvalue(self.opt, self.path, True, index, undefined, True) if self.opt.impl_is_submulti(): value = SubMulti(value, self.context, self.opt, self.path, index) return value def append(self, value=undefined, force=False, setitem=True, validate=True, force_permissive=False): """the list value can be updated (appened) only if the option is a master """ if not force and self.opt.impl_is_master_slaves('slave'): raise SlaveError(_("cannot append a value on a multi option {0}" " which is a slave").format(self.opt.impl_getname())) index = self.__len__() if value is undefined: value = self._getdefaultvalue(index) if validate and value not in [None, undefined]: context = self._getcontext() setting = context.cfgimpl_get_settings() setting_properties = setting._getproperties(read_write=False) if 'validator' in setting_properties: session = context.cfgimpl_get_values()._p_.getsession() fake_context = context._gen_fake_values(session) fake_multi = fake_context.cfgimpl_get_values()._get_cached_value( self.opt, path=self.path, validate=False, force_permissive=force_permissive) if isinstance(fake_multi, Exception): raise fake_multi fake_multi.append(value, validate=False, force=True, setitem=setitem) self._validate(value, fake_context, index, True) if not '_index' in self.__slots__ and self.opt.impl_is_submulti(): if not isinstance(value, SubMulti): value = SubMulti(value, self.context, self.opt, self.path, index) value.submulti = weakref.ref(self) super(Multi, self).append(value) if setitem: self._store(force=force) def append_properties_error(self, err): super(Multi, self).append(err) def sort(self, cmp=None, key=None, reverse=False): if self.opt.impl_is_master_slaves(): raise SlaveError(_("cannot sort multi option {0} if master or slave" "").format(self.opt.impl_getname())) if sys.version_info[0] >= 3: # pragma: no cover if cmp is not None: raise ValueError(_('cmp is not permitted in python v3 or ' 'greater')) super(Multi, self).sort(key=key, reverse=reverse) else: super(Multi, self).sort(cmp=cmp, key=key, reverse=reverse) self._store() def reverse(self): if self.opt.impl_is_master_slaves(): raise SlaveError(_("cannot reverse multi option {0} if master or " "slave").format(self.opt.impl_getname())) super(Multi, self).reverse() self._store() def insert(self, index, value, validate=True): if self.opt.impl_is_master_slaves(): raise SlaveError(_("cannot insert multi option {0} if master or " "slave").format(self.opt.impl_getname())) context = self._getcontext() setting = setting = context.cfgimpl_get_settings() setting_properties = setting._getproperties(read_write=False) if 'validator' in setting_properties and validate and value is not None: session = context.cfgimpl_get_values()._p_.getsession() fake_context = context._gen_fake_values(session) fake_multi = fake_context.cfgimpl_get_values()._get_cached_value( self.opt, path=self.path, validate=False) fake_multi.insert(index, value, validate=False) self._validate(value, fake_context, index, True) super(Multi, self).insert(index, value) self._store() def extend(self, iterable, validate=True): if self.opt.impl_is_master_slaves(): raise SlaveError(_("cannot extend multi option {0} if master or " "slave").format(self.opt.impl_getname())) index = getattr(self, '_index', None) context = self._getcontext() setting = context.cfgimpl_get_settings() setting_properties = setting._getproperties(read_write=False) if 'validator' in setting_properties and validate: session = context.cfgimpl_get_values()._p_.getsession() fake_context = context._gen_fake_values(session) fake_multi = fake_context.cfgimpl_get_values()._get_cached_value( self.opt, path=self.path, validate=False) if index is None: fake_multi.extend(iterable, validate=False) self._validate(fake_multi, fake_context, index) else: fake_multi[index].extend(iterable, validate=False) self._validate(fake_multi[index], fake_context, index) super(Multi, self).extend(iterable) self._store() def _validate(self, value, fake_context, force_index, submulti=False): err = self.opt.impl_validate(value, context=fake_context, force_index=force_index, multi=self) if err: raise err def pop(self, index, force=False): """the list value can be updated (poped) only if the option is a master :param index: remove item a index :type index: int :param force: force pop item (withoud check master/slave) :type force: boolean :returns: item at index """ context = self._getcontext() if not force: if self.opt.impl_is_master_slaves('slave'): raise SlaveError(_("cannot pop a value on a multi option {0}" " which is a slave").format(self.opt.impl_getname())) if self.opt.impl_is_master_slaves('master'): self.opt.impl_get_master_slaves().pop(self.opt, context.cfgimpl_get_values(), index) #set value without valid properties ret = super(Multi, self).pop(index) self._store(force=force) return ret def _store(self, force=False, index=None): values = self._getcontext().cfgimpl_get_values() if not force: #FIXME could get properties an pass it values.validate(self.opt, self, self.path, valid_masterslave=False) values._setvalue(self.opt, self.path, self, index=index) class SubMulti(Multi): __slots__ = ('_index', 'submulti') def __init__(self, value, context, opt, path, index): """ :param index: index (only for slave with submulti) :type index: `int` """ self._index = index super(SubMulti, self).__init__(value, context, opt, path) def append(self, value=undefined): super(SubMulti, self).append(value, force=True) def pop(self, index): return super(SubMulti, self).pop(index, force=True) def __setitem__(self, index, value): self._setitem(index, value) def _store(self, force=False, index=None): #force is unused here values = self._getcontext().cfgimpl_get_values() values.validate(self.opt, self, self.path, valid_masterslave=False) values._setvalue(self.opt, self.path, self.submulti()) def _validate(self, value, fake_context, force_index, submulti=False): if value is not None: if submulti is False: super(SubMulti, self)._validate(value, fake_context, force_index, submulti) else: err = self.opt.impl_validate(value, context=fake_context, force_index=self._index, force_submulti_index=force_index, multi=self) if err: raise err def _getdefaultvalue(self, index): values = self._getcontext().cfgimpl_get_values() return values._getdefaultvalue(self.opt, self.path, True, index, self._index, True)