valid default/callback value in consistencies
[tiramisu.git] / tiramisu / value.py
1 # -*- coding: utf-8 -*-
2 "takes care of the option's values and multi values"
3 # Copyright (C) 2013-2014 Team tiramisu (see AUTHORS for all contributors)
4 #
5 # This program is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU Lesser General Public License as published by the
7 # Free Software Foundation, either version 3 of the License, or (at your
8 # option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
13 # details.
14 #
15 # You should have received a copy of the GNU Lesser General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 # ____________________________________________________________
18 from time import time
19 import sys
20 import weakref
21 from tiramisu.error import ConfigError, SlaveError, PropertiesOptionError
22 from tiramisu.setting import owners, expires_time, undefined
23 from tiramisu.autolib import carry_out_calculation
24 from tiramisu.i18n import _
25 from tiramisu.option import SymLinkOption, DynSymLinkOption, Option
26
27
28 class Values(object):
29     """The `Config`'s root is indeed  in charge of the `Option()`'s values,
30     but the values are physicaly located here, in `Values`, wich is also
31     responsible of a caching utility.
32     """
33     __slots__ = ('context', '_p_', '__weakref__')
34
35     def __init__(self, context, storage):
36         """
37         Initializes the values's dict.
38
39         :param context: the context is the home config's values
40
41         """
42         self.context = weakref.ref(context)
43         # the storage type is dictionary or sqlite3
44         self._p_ = storage
45
46     def _getcontext(self):
47         """context could be None, we need to test it
48         context is None only if all reference to `Config` object is deleted
49         (for example we delete a `Config` and we manipulate a reference to
50         old `SubConfig`, `Values`, `Multi` or `Settings`)
51         """
52         context = self.context()
53         if context is None:  # pragma: optional cover
54             raise ConfigError(_('the context does not exist anymore'))
55         return context
56
57     def _getvalue(self, opt, path, is_default, index=undefined,
58                   with_meta=True, setting_properties=undefined):
59         """actually retrieves the value
60
61         :param opt: the `option.Option()` object
62         :returns: the option's value (or the default value if not set)
63         """
64         if opt.impl_is_optiondescription():  # pragma: optional cover
65             raise ValueError(_('optiondescription has no value'))
66         setting = self._getcontext().cfgimpl_get_settings()
67         force_default = 'frozen' in setting._getitem(opt, path,
68                                                      self_properties=setting_properties) and \
69             'force_default_on_freeze' in setting._getitem(opt, path,
70                                                           self_properties=setting_properties)
71         if not is_default and not force_default:
72             value = self._p_.getvalue(path)
73             if index is not undefined:
74                 try:
75                     return value[index]
76                 except IndexError:
77                     #value is smaller than expected
78                     #so return default value
79                     pass
80             else:
81                 return value
82         #so default value
83         # if value has callback and is not set
84         if opt.impl_has_callback():
85             callback, callback_params = opt.impl_get_callback()
86             value = carry_out_calculation(opt, config=self._getcontext(),
87                                           callback=callback,
88                                           callback_params=callback_params,
89                                           index=index)
90             try:
91                 if isinstance(value, list) and index is not undefined:
92                     #if submulti and return a list of list, just return list
93                     if opt.impl_is_submulti():
94                         val = value[index]
95                         if isinstance(val, list):
96                             value = val
97                     else:
98                         value = value[index]
99                 return value
100             except IndexError:
101                 pass
102         if with_meta:
103             meta = self._getcontext().cfgimpl_get_meta()
104             if meta is not None:
105                 #FIXME : possible problème de longueur si slave en SymLinkOption
106                 try:
107                     value = meta.cfgimpl_get_values(
108                     )._get_cached_item(opt, path)
109                     if isinstance(value, Multi):
110                         if index is not undefined:
111                             value = value[index]
112                         else:
113                             value = list(value)
114                     return value
115                 except PropertiesOptionError:
116                     pass
117         # now try to get default value
118         value = opt.impl_getdefault()
119         if opt.impl_is_multi() and index is not undefined:
120             if value is None:
121                 value = opt.impl_getdefault_multi()
122             else:
123                 try:
124                     value = value[index]
125                 except IndexError:
126                     value = opt.impl_getdefault_multi()
127         return value
128
129     def get_modified_values(self):
130         context = self._getcontext()
131         if context._impl_descr is not None:
132             for opt, path in context.cfgimpl_get_description(
133             )._get_force_store_value():
134                 self._getowner(opt, path, force_permissive=True)
135         return self._p_.get_modified_values()
136
137     def __contains__(self, opt):
138         """
139         implements the 'in' keyword syntax in order provide a pythonic way
140         to kow if an option have a value
141
142         :param opt: the `option.Option()` object
143         """
144         path = opt.impl_getpath(self._getcontext())
145         return self._contains(path)
146
147     def _contains(self, path):
148         return self._p_.hasvalue(path)
149
150     def __delitem__(self, opt):
151         """overrides the builtins `del()` instructions"""
152         self.reset(opt)
153
154     def reset(self, opt, path=None, validate=True):
155         if path is None:
156             path = opt.impl_getpath(self._getcontext())
157         context = self._getcontext()
158         if validate:
159             context.cfgimpl_get_settings().validate_properties(opt, False,
160                                                                True, path)
161         if self._contains(path):
162             if validate:
163                 setting = context.cfgimpl_get_settings()
164                 opt.impl_validate(opt.impl_getdefault(),
165                                   context, 'validator' in setting)
166             context.cfgimpl_reset_cache()
167             if opt.impl_is_master_slaves('master'):
168                 opt.impl_get_master_slaves().reset(opt, self)
169             self._p_.resetvalue(path)
170
171     def _isempty(self, opt, value):
172         "convenience method to know if an option is empty"
173         empty = opt._empty
174         if value is not undefined:
175             empty_not_multi = not opt.impl_is_multi() and (value is None or
176                                                            value == empty)
177             empty_multi = opt.impl_is_multi() and (value == [] or
178                                                    None in value or
179                                                    empty in value)
180         else:
181             empty_multi = empty_not_multi = False
182         return empty_not_multi or empty_multi
183
184     def __getitem__(self, opt):
185         "enables us to use the pythonic dictionary-like access to values"
186         return self.getitem(opt)
187
188     def getitem(self, opt, validate=True, force_permissive=False):
189         """
190         """
191         return self._get_cached_item(opt, validate=validate,
192                                      force_permissive=force_permissive)
193
194     def _get_cached_item(self, opt, path=None, validate=True,
195                          force_permissive=False, force_properties=None,
196                          validate_properties=True,
197                          setting_properties=undefined):
198         if path is None:
199             path = opt.impl_getpath(self._getcontext())
200         ntime = None
201         if setting_properties is undefined:
202             setting_properties = self._getcontext().cfgimpl_get_settings(
203             )._getproperties()
204         if 'cache' in setting_properties and self._p_.hascache(path):
205             if 'expire' in setting_properties:
206                 ntime = int(time())
207             is_cached, value = self._p_.getcache(path, ntime)
208             if is_cached:
209                 if opt.impl_is_multi() and not isinstance(value, Multi):
210                     #load value so don't need to validate if is not a Multi
211                     value = Multi(value, self.context, opt, path)
212                 return value
213         val = self._getitem(opt, path, validate, force_permissive,
214                             force_properties, validate_properties,
215                             setting_properties)
216         if 'cache' in setting_properties and validate and validate_properties \
217                 and force_permissive is False and force_properties is None:
218             if 'expire' in setting_properties:
219                 if ntime is None:
220                     ntime = int(time())
221                 ntime = ntime + expires_time
222             self._p_.setcache(path, val, ntime)
223         return val
224
225     def _getitem(self, opt, path, validate, force_permissive, force_properties,
226                  validate_properties, setting_properties=undefined):
227         if opt.impl_is_master_slaves():
228             return opt.impl_get_master_slaves().getitem(self, opt, path,
229                                                         validate,
230                                                         force_permissive,
231                                                         force_properties,
232                                                         validate_properties,
233                                                         setting_properties=setting_properties)
234         else:
235             return self._get_validated_value(opt, path, validate,
236                                              force_permissive,
237                                              force_properties,
238                                              validate_properties,
239                                              setting_properties=setting_properties)
240
241     def _get_validated_value(self, opt, path, validate, force_permissive,
242                              force_properties, validate_properties,
243                              index=undefined, submulti_index=undefined,
244                              with_meta=True, setting_properties=undefined):
245         """same has getitem but don't touch the cache
246         index is None for slave value, if value returned is not a list, just return []
247         """
248         context = self._getcontext()
249         setting = context.cfgimpl_get_settings()
250         is_default = self._is_default_owner(opt, path,
251                                             validate_properties=False,
252                                             validate_meta=False,
253                                             setting_properties=setting_properties)
254         try:
255             if index is None:
256                 gv_index = undefined
257             else:
258                 gv_index = index
259             value = self._getvalue(opt, path, is_default, index=gv_index,
260                                    with_meta=with_meta,
261                                    setting_properties=setting_properties)
262             config_error = None
263         except ConfigError as err:
264             # For calculating properties, we need value (ie for mandatory
265             # value).
266             # If value is calculating with a PropertiesOptionError's option
267             # _getvalue raise a ConfigError.
268             # We can not raise ConfigError if this option should raise
269             # PropertiesOptionError too. So we get config_error and raise
270             # ConfigError if properties did not raise.
271             # cannot assign config_err directly in python 3.3
272             config_error = err
273             # value is not set, for 'undefined' (cannot set None because of
274             # mandatory property)
275             value = undefined
276
277         if config_error is None:
278             if index is undefined:
279                 force_index = None
280             else:
281                 force_index = index
282             if opt.impl_is_multi():
283                 #for slave is a multi
284                 if index is None and not isinstance(value, list):
285                     value = []
286                 if force_index is None:
287                     value = Multi(value, self.context, opt, path)
288                 elif opt.impl_is_submulti() and submulti_index is undefined:
289                     value = SubMulti(value, self.context, opt, path,
290                                      force_index)
291
292             if validate:
293                 if submulti_index is undefined:
294                     force_submulti_index = None
295                 else:
296                     force_submulti_index = submulti_index
297                 if setting_properties is undefined:
298                     setting_properties = setting._getproperties()
299                 opt.impl_validate(value, context,
300                                   'validator' in setting_properties,
301                                   force_index=force_index,
302                                   force_submulti_index=force_submulti_index)
303             #FIXME pas de test avec les metas ...
304             #FIXME et les symlinkoption ...
305             if is_default and 'force_store_value' in setting._getitem(opt,
306                                                                       path,
307                                                                       self_properties=setting_properties):
308                 if isinstance(value, Multi):
309                     item = list(value)
310                 else:
311                     item = value
312                 self.setitem(opt, item, path, is_write=False,
313                              force_permissive=force_permissive)
314         if validate_properties:
315             setting.validate_properties(opt, False, False, value=value,
316                                         path=path,
317                                         force_permissive=force_permissive,
318                                         force_properties=force_properties,
319                                         self_properties=setting_properties)
320         if config_error is not None:
321             raise config_error
322         return value
323
324     def __setitem__(self, opt, value):  # pragma: optional cover
325         raise ConfigError(_('you should only set value with config'))
326
327     def setitem(self, opt, value, path, force_permissive=False,
328                 is_write=True):
329         # is_write is, for example, used with "force_store_value"
330         # user didn't change value, so not write
331         # valid opt
332         context = self._getcontext()
333         setting_properties = context.cfgimpl_get_settings()._getproperties()
334         opt.impl_validate(value, context,
335                           'validator' in setting_properties)
336         if opt.impl_is_master_slaves():
337             opt.impl_get_master_slaves().setitem(self, opt, value, path)
338         self._setvalue(opt, path, value, force_permissive=force_permissive,
339                        is_write=is_write,
340                        setting_properties=setting_properties)
341
342     def _setvalue(self, opt, path, value, force_permissive=False,
343                   is_write=True, validate_properties=True,
344                   setting_properties=undefined):
345         context = self._getcontext()
346         context.cfgimpl_reset_cache()
347         if validate_properties:
348             setting = context.cfgimpl_get_settings()
349             setting.validate_properties(opt, False, is_write,
350                                         value=value, path=path,
351                                         force_permissive=force_permissive,
352                                         self_properties=setting_properties)
353         owner = context.cfgimpl_get_settings().getowner()
354         if isinstance(value, Multi):
355             value = list(value)
356             if opt.impl_is_submulti():
357                 for idx, val in enumerate(value):
358                     if isinstance(val, SubMulti):
359                         value[idx] = list(val)
360         self._p_.setvalue(path, value, owner)
361
362     def _is_meta(self, opt, path):
363         context = self._getcontext()
364         setting = context.cfgimpl_get_settings()
365         settings = setting._getitem(opt, path)
366         if 'frozen' in settings and 'force_default_on_freeze' in settings:
367             return False
368         if self._p_.getowner(path, owners.default) is not owners.default:
369             return False
370         if context.cfgimpl_get_meta() is not None:
371             return True
372         return False
373
374     def getowner(self, opt, force_permissive=False):
375         """
376         retrieves the option's owner
377
378         :param opt: the `option.Option` object
379         :param force_permissive: behaves as if the permissive property
380                                  was present
381         :returns: a `setting.owners.Owner` object
382         """
383         if isinstance(opt, SymLinkOption) and \
384                 not isinstance(opt, DynSymLinkOption):
385             opt = opt._impl_getopt()
386         path = opt.impl_getpath(self._getcontext())
387         return self._getowner(opt, path, force_permissive=force_permissive)
388
389     def _getowner(self, opt, path, validate_properties=True,
390                   force_permissive=False, validate_meta=undefined,
391                   setting_properties=undefined):
392         if not isinstance(opt, Option) and not isinstance(opt,
393                                                           DynSymLinkOption):
394             raise ConfigError(_('owner only avalaible for an option'))
395         context = self._getcontext()
396         setting = context.cfgimpl_get_settings()
397         settings = setting._getitem(opt, path,
398                                     self_properties=setting_properties)
399         if 'frozen' in settings and 'force_default_on_freeze' in settings:
400             return owners.default
401         if validate_properties:
402             self._getitem(opt, path, True, force_permissive, None, True)
403         owner = self._p_.getowner(path, owners.default)
404         if validate_meta is undefined:
405             if opt.impl_is_master_slaves('slave'):
406                 master = opt.impl_get_master_slaves().getmaster(opt)
407                 masterp = master.impl_getpath(context)
408                 validate_meta = self._is_meta(opt, masterp)
409             else:
410                 validate_meta = True
411         if validate_meta:
412             meta = context.cfgimpl_get_meta()
413             if owner is owners.default and meta is not None:
414                 owner = meta.cfgimpl_get_values()._getowner(opt, path)
415         return owner
416
417     def setowner(self, opt, owner):
418         """
419         sets a owner to an option
420
421         :param opt: the `option.Option` object
422         :param owner: a valid owner, that is a `setting.owners.Owner` object
423         """
424         if not isinstance(owner, owners.Owner):  # pragma: optional cover
425             raise TypeError(_("invalid generic owner {0}").format(str(owner)))
426
427         path = opt.impl_getpath(self._getcontext())
428         self._setowner(opt, path, owner)
429
430     def _setowner(self, opt, path, owner):
431         if not self._p_.hasvalue(path):  # pragma: optional cover
432             raise ConfigError(_('no value for {0} cannot change owner to {1}'
433                                 '').format(path, owner))
434         self._getcontext().cfgimpl_get_settings().validate_properties(opt,
435                                                                       False,
436                                                                       True,
437                                                                       path)
438
439         self._p_.setowner(path, owner)
440
441     def is_default_owner(self, opt, validate_properties=True,
442                          validate_meta=True):
443         """
444         :param config: *must* be only the **parent** config
445                        (not the toplevel config)
446         :return: boolean
447         """
448         path = opt.impl_getpath(self._getcontext())
449         return self._is_default_owner(opt, path,
450                                       validate_properties=validate_properties,
451                                       validate_meta=validate_meta)
452
453     def _is_default_owner(self, opt, path, validate_properties=True,
454                           validate_meta=True, setting_properties=undefined):
455         return self._getowner(opt, path, validate_properties,
456                               validate_meta=validate_meta,
457                               setting_properties=setting_properties) == \
458             owners.default
459
460     def reset_cache(self, only_expired):
461         """
462         clears the cache if necessary
463         """
464         if only_expired:
465             self._p_.reset_expired_cache(int(time()))
466         else:
467             self._p_.reset_all_cache()
468
469     # information
470     def set_information(self, key, value):
471         """updates the information's attribute
472
473         :param key: information's key (ex: "help", "doc"
474         :param value: information's value (ex: "the help string")
475         """
476         self._p_.set_information(key, value)
477
478     def get_information(self, key, default=undefined):
479         """retrieves one information's item
480
481         :param key: the item string (ex: "help")
482         """
483         try:
484             return self._p_.get_information(key)
485         except ValueError:  # pragma: optional cover
486             if default is not undefined:
487                 return default
488             else:
489                 raise ValueError(_("information's item"
490                                    " not found: {0}").format(key))
491
492     def mandatory_warnings(self, force_permissive=False):
493         """convenience function to trace Options that are mandatory and
494         where no value has been set
495
496         :returns: generator of mandatory Option's path
497
498         """
499         def _mandatory_warnings(description):
500             #if value in cache, properties are not calculated
501             _ret = []
502             for opt in description._impl_getchildren(
503                     context=self._getcontext()):
504                 if opt.impl_is_optiondescription():
505                     _ret.extend(_mandatory_warnings(opt))
506                 elif isinstance(opt, SymLinkOption) and \
507                         not isinstance(opt, DynSymLinkOption):
508                     pass
509                 else:
510                     path = opt.impl_getpath(self._getcontext())
511                     try:
512                         self._get_cached_item(opt, path=path,
513                                               force_properties=frozenset(('mandatory',)),
514                                               force_permissive=force_permissive)
515                     except PropertiesOptionError as err:
516                         if err.proptype == ['mandatory']:
517                             _ret.append(path)
518             return _ret
519         self.reset_cache(False)
520         descr = self._getcontext().cfgimpl_get_description()
521         ret = _mandatory_warnings(descr)
522         self.reset_cache(False)
523         return ret
524
525     def force_cache(self):
526         """parse all option to force data in cache
527         """
528         context = self.context()
529         if not 'cache' in context.cfgimpl_get_settings():
530             raise ConfigError(_('can force cache only if cache '
531                                 'is actived in config'))
532         #remove all cached properties and value to update "expired" time
533         context.cfgimpl_reset_cache()
534         for path in context.cfgimpl_get_description().impl_getpaths(
535                 include_groups=True):
536             try:
537                 context.getattr(path)
538             except PropertiesOptionError:
539                 pass
540
541     def __getstate__(self):
542         return {'_p_': self._p_}
543
544     def _impl_setstate(self, storage):
545         self._p_._storage = storage
546
547     def __setstate__(self, states):
548         self._p_ = states['_p_']
549
550
551 # ____________________________________________________________
552 # multi types
553
554 class Multi(list):
555     """multi options values container
556     that support item notation for the values of multi options"""
557     __slots__ = ('opt', 'path', 'context', '__weakref__')
558
559     def __init__(self, value, context, opt, path):
560         """
561         :param value: the Multi wraps a list value
562         :param context: the home config that has the values
563         :param opt: the option object that have this Multi value
564         :param path: path of the option
565         """
566         if value is None:
567             value = []
568         if not opt.impl_is_submulti() and isinstance(value, Multi):  # pragma: optional cover
569             raise ValueError(_('{0} is already a Multi ').format(
570                 opt.impl_getname()))
571         self.opt = opt
572         self.path = path
573         if not isinstance(context, weakref.ReferenceType):  # pragma: optional cover
574             raise ValueError('context must be a Weakref')
575         self.context = context
576         if not isinstance(value, list):
577             if not '_index' in self.__slots__ and opt.impl_is_submulti():
578                 value = [[value]]
579             else:
580                 value = [value]
581         elif value != [] and not '_index' in self.__slots__ and \
582                 opt.impl_is_submulti() and not isinstance(value[0], list):
583             value = [value]
584         super(Multi, self).__init__(value)
585         if opt.impl_is_submulti():
586             if not '_index' in self.__slots__:
587                 for idx, val in enumerate(self):
588                     if not isinstance(val, SubMulti):
589                         super(Multi, self).__setitem__(idx, SubMulti(val,
590                                                                      context,
591                                                                      opt, path,
592                                                                      idx))
593                     #FIXME weakref ??
594                     self[idx].submulti = weakref.ref(self)
595
596     def _getcontext(self):
597         """context could be None, we need to test it
598         context is None only if all reference to `Config` object is deleted
599         (for example we delete a `Config` and we manipulate a reference to
600         old `SubConfig`, `Values`, `Multi` or `Settings`)
601         """
602         context = self.context()
603         if context is None:  # pragma: optional cover
604             raise ConfigError(_('the context does not exist anymore'))
605         return context
606
607     def __setitem__(self, index, value):
608         self._setitem(index, value)
609
610     def _setitem(self, index, value):
611         self._validate(value, index, True)
612         #assume not checking mandatory property
613         super(Multi, self).__setitem__(index, value)
614         self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path,
615                                                           self)
616
617     #def __repr__(self, *args, **kwargs):
618     #    return super(Multi, self).__repr__(*args, **kwargs)
619
620     #def __getitem__(self, y):
621     #    return super(Multi, self).__getitem__(y)
622
623     def _get_validated_value(self, index):
624         values = self._getcontext().cfgimpl_get_values()
625         return values._get_validated_value(self.opt, self.path,
626                                            True, False, None, True,
627                                            index=index)
628
629     def append(self, value=undefined, force=False, setitem=True):
630         """the list value can be updated (appened)
631         only if the option is a master
632         """
633         if not force and self.opt.impl_is_master_slaves('slave'):  # pragma: optional cover
634             raise SlaveError(_("cannot append a value on a multi option {0}"
635                                " which is a slave").format(self.opt.impl_getname()))
636         index = self.__len__()
637         if value is undefined:
638             try:
639                 value = self._get_validated_value(index)
640             except IndexError:
641                 value = None
642         self._validate(value, index, True)
643         if not '_index' in self.__slots__ and self.opt.impl_is_submulti():
644             if not isinstance(value, SubMulti):
645                 value = SubMulti(value, self.context, self.opt, self.path, index)
646             value.submulti = weakref.ref(self)
647         super(Multi, self).append(value)
648         if setitem:
649             self._store(force)
650
651     def sort(self, cmp=None, key=None, reverse=False):
652         if self.opt.impl_is_master_slaves():
653             raise SlaveError(_("cannot sort multi option {0} if master or slave"
654                                "").format(self.opt.impl_getname()))
655         if sys.version_info[0] >= 3:
656             if cmp is not None:
657                 raise ValueError(_('cmp is not permitted in python v3 or '
658                                    'greater'))
659             super(Multi, self).sort(key=key, reverse=reverse)
660         else:
661             super(Multi, self).sort(cmp=cmp, key=key, reverse=reverse)
662         self._store()
663
664     def reverse(self):
665         if self.opt.impl_is_master_slaves():
666             raise SlaveError(_("cannot reverse multi option {0} if master or "
667                                "slave").format(self.opt.impl_getname()))
668         super(Multi, self).reverse()
669         self._store()
670
671     def insert(self, index, obj):
672         #FIXME obj should be undefined
673         if self.opt.impl_is_master_slaves():
674             raise SlaveError(_("cannot insert multi option {0} if master or "
675                                "slave").format(self.opt.impl_getname()))
676         self._validate(obj, index, True)
677         super(Multi, self).insert(index, obj)
678         self._store()
679
680     def extend(self, iterable):
681         if self.opt.impl_is_master_slaves():
682             raise SlaveError(_("cannot extend multi option {0} if master or "
683                                "slave").format(self.opt.impl_getname()))
684         try:
685             index = self._index
686         except:
687             index = None
688         self._validate(iterable, index)
689         super(Multi, self).extend(iterable)
690         self._store()
691
692     def _validate(self, value, force_index, submulti=False):
693         if value is not None:
694             self.opt.impl_validate(value, context=self._getcontext(),
695                                    force_index=force_index)
696
697     def pop(self, index, force=False):
698         """the list value can be updated (poped)
699         only if the option is a master
700
701         :param index: remove item a index
702         :type index: int
703         :param force: force pop item (withoud check master/slave)
704         :type force: boolean
705         :returns: item at index
706         """
707         context = self._getcontext()
708         if not force:
709             if self.opt.impl_is_master_slaves('slave'):  # pragma: optional cover
710                 raise SlaveError(_("cannot pop a value on a multi option {0}"
711                                    " which is a slave").format(self.opt.impl_getname()))
712             if self.opt.impl_is_master_slaves('master'):
713                 self.opt.impl_get_master_slaves().pop(self.opt,
714                                                       context.cfgimpl_get_values(), index)
715         #set value without valid properties
716         ret = super(Multi, self).pop(index)
717         self._store(force)
718         return ret
719
720     def _store(self, force=False):
721         self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path,
722                                                           self,
723                                                           validate_properties=not force)
724
725
726 class SubMulti(Multi):
727     __slots__ = ('_index', 'submulti')
728
729     def __init__(self, value, context, opt, path, index):
730         """
731         :param index: index (only for slave with submulti)
732         :type index: `int`
733         """
734         self._index = index
735         super(SubMulti, self).__init__(value, context, opt, path)
736
737     def append(self, value=undefined):
738         super(SubMulti, self).append(value, force=True)
739
740     def pop(self, index):
741         return super(SubMulti, self).pop(index, force=True)
742
743     def __setitem__(self, index, value):
744         self._setitem(index, value)
745
746     def _store(self, force=False):
747         #force is unused here
748         self._getcontext().cfgimpl_get_values()._setvalue(self.opt,
749                                                           self.path,
750                                                           self.submulti())
751
752     def _validate(self, value, force_index, submulti=False):
753         if value is not None:
754             if submulti is False:
755                 super(SubMulti, self)._validate(value, force_index)
756             else:
757                 self.opt.impl_validate(value, context=self._getcontext(),
758                                        force_index=self._index,
759                                        force_submulti_index=force_index)
760
761     def _get_validated_value(self, index):
762         values = self._getcontext().cfgimpl_get_values()
763         return values._get_validated_value(self.opt, self.path,
764                                            True, False, None, True,
765                                            index=index,
766                                            submulti_index=self._index)