c5f45e55cf6dc279b087cd4f3b8fef727840bfc8
[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, settings=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
67         if settings is undefined:
68             settings = self._getcontext().cfgimpl_get_settings()._getproperties(
69                 opt, path, read_write=False)
70         force_default = 'frozen' in settings and \
71             'force_default_on_freeze' in settings
72         if not is_default and not force_default:
73             value = self._p_.getvalue(path)
74             if index is not undefined:
75                 try:
76                     return value[index]
77                 except IndexError:
78                     #value is smaller than expected
79                     #so return default value
80                     pass
81             else:
82                 return value
83         #so default value
84         # if value has callback and is not set
85         if opt.impl_has_callback():
86             callback, callback_params = opt.impl_get_callback()
87             value = carry_out_calculation(opt, context=self._getcontext(),
88                                           callback=callback,
89                                           callback_params=callback_params,
90                                           index=index)
91             try:
92                 if isinstance(value, list) and index is not undefined:
93                     #if submulti and return a list of list, just return list
94                     if opt.impl_is_submulti():
95                         val = value[index]
96                         if isinstance(val, list):
97                             value = val
98                     else:
99                         value = value[index]
100                 return value
101             except IndexError:
102                 pass
103         if with_meta:
104             meta = self._getcontext().cfgimpl_get_meta()
105             if meta is not None:
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
162         hasvalue = self._contains(path)
163
164         setting = context.cfgimpl_get_settings()
165         setting_properties = setting._getproperties(read_write=False)
166         if 'validator' in setting_properties and validate and hasvalue:
167             fake_context = context._gen_fake_values()
168             fake_value = fake_context.cfgimpl_get_values()
169             fake_value.reset(opt, path, validate=False)
170             opt.impl_validate(getattr(fake_context, path),
171                               fake_context, 'validator' in setting_properties)
172         context.cfgimpl_reset_cache()
173         if opt.impl_is_master_slaves('master'):
174             opt.impl_get_master_slaves().reset(opt, self)
175         if hasvalue:
176             self._p_.resetvalue(path)
177
178     def _isempty(self, opt, value, force_allow_empty_list=False):
179         "convenience method to know if an option is empty"
180         if value is undefined:
181             return False
182         else:
183             empty = opt._empty
184             if opt.impl_is_multi():
185                 if force_allow_empty_list:
186                     allow_empty_list = True
187                 else:
188                     allow_empty_list = opt.impl_allow_empty_list()
189                     if allow_empty_list is undefined:
190                         if opt.impl_is_master_slaves('slave'):
191                             allow_empty_list = True
192                         else:
193                             allow_empty_list = False
194                 isempty = (not allow_empty_list and value == []) or \
195                     None in value or empty in value
196             else:
197                 isempty = value is None or value == empty
198         return isempty
199
200     def __getitem__(self, opt):
201         "enables us to use the pythonic dictionary-like access to values"
202         return self.getitem(opt)
203
204     def getitem(self, opt, validate=True, force_permissive=False):
205         """
206         """
207         return self._get_cached_item(opt, validate=validate,
208                                      force_permissive=force_permissive)
209
210     def _get_cached_item(self, opt, path=None, validate=True,
211                          force_permissive=False, force_properties=None,
212                          validate_properties=True,
213                          setting_properties=undefined):
214         if path is None:
215             path = opt.impl_getpath(self._getcontext())
216         ntime = None
217         if setting_properties is undefined:
218             setting_properties = self._getcontext().cfgimpl_get_settings(
219             )._getproperties(read_write=False)
220         settings = self._getcontext().cfgimpl_get_settings()._getproperties(
221             opt, path, read_write=False, self_properties=setting_properties)
222         if 'cache' in setting_properties and self._p_.hascache(path):
223             if 'expire' in setting_properties:
224                 ntime = int(time())
225             is_cached, value = self._p_.getcache(path, ntime)
226             if is_cached:
227                 if opt.impl_is_multi() and not isinstance(value, Multi):
228                     #load value so don't need to validate if is not a Multi
229                     value = Multi(value, self.context, opt, path)
230                 return value
231         val = self._getitem(opt, path, validate, force_permissive,
232                             force_properties, validate_properties,
233                             setting_properties, settings=settings)
234         if 'cache' in setting_properties and validate and validate_properties \
235                 and force_permissive is False and force_properties is None:
236             if 'expire' in setting_properties:
237                 if ntime is None:
238                     ntime = int(time())
239                 ntime = ntime + expires_time
240             self._p_.setcache(path, val, ntime)
241         return val
242
243     def _getitem(self, opt, path, validate, force_permissive, force_properties,
244                  validate_properties, setting_properties=undefined,
245                  settings=undefined):
246         if opt.impl_is_master_slaves():
247             return opt.impl_get_master_slaves().getitem(self, opt, path,
248                                                         validate,
249                                                         force_permissive,
250                                                         force_properties,
251                                                         validate_properties,
252                                                         setting_properties=setting_properties,
253                                                         settings=settings)
254         else:
255             return self._get_validated_value(opt, path, validate,
256                                              force_permissive,
257                                              force_properties,
258                                              validate_properties,
259                                              setting_properties=setting_properties,
260                                              settings=settings)
261
262     def _get_validated_value(self, opt, path, validate, force_permissive,
263                              force_properties, validate_properties,
264                              index=undefined, submulti_index=undefined,
265                              with_meta=True, setting_properties=undefined,
266                              settings=undefined):
267         """same has getitem but don't touch the cache
268         index is None for slave value, if value returned is not a list, just return []
269         """
270         context = self._getcontext()
271         setting = context.cfgimpl_get_settings()
272         if setting_properties is undefined:
273             setting_properties = setting._getproperties(read_write=False)
274         if settings is undefined:
275             settings = setting._getproperties(opt, path, read_write=False)
276         is_default = self._is_default_owner(opt, path,
277                                             validate_properties=False,
278                                             validate_meta=False,
279                                             settings=settings)
280         try:
281             if index is None:
282                 gv_index = undefined
283             else:
284                 gv_index = index
285             value = self._getvalue(opt, path, is_default, index=gv_index,
286                                    with_meta=with_meta,
287                                    settings=settings)
288             config_error = None
289         except ConfigError as err:
290             # For calculating properties, we need value (ie for mandatory
291             # value).
292             # If value is calculating with a PropertiesOptionError's option
293             # _getvalue raise a ConfigError.
294             # We can not raise ConfigError if this option should raise
295             # PropertiesOptionError too. So we get config_error and raise
296             # ConfigError if properties did not raise.
297             # cannot assign config_err directly in python 3.3
298             config_error = err
299             # value is not set, for 'undefined' (cannot set None because of
300             # mandatory property)
301             value = undefined
302
303         if config_error is None:
304             if index is undefined:
305                 force_index = None
306             else:
307                 force_index = index
308             if opt.impl_is_multi():
309                 #for slave is a multi
310                 if index is None and not isinstance(value, list):
311                     value = []
312                 if force_index is None:
313                     value = Multi(value, self.context, opt, path)
314                 elif opt.impl_is_submulti() and submulti_index is undefined:
315                     value = SubMulti(value, self.context, opt, path,
316                                      force_index)
317
318             if validate:
319                 if submulti_index is undefined:
320                     force_submulti_index = None
321                 else:
322                     force_submulti_index = submulti_index
323                 try:
324                     opt.impl_validate(value, context,
325                                       'validator' in setting_properties,
326                                       force_index=force_index,
327                                       force_submulti_index=force_submulti_index)
328                 except ValueError, err:
329                     config_error = err
330                     value = None
331
332             if is_default and 'force_store_value' in settings:
333                 if isinstance(value, Multi):
334                     item = list(value)
335                 else:
336                     item = value
337                 self.setitem(opt, item, path, is_write=False,
338                              force_permissive=force_permissive)
339         if validate_properties:
340             setting.validate_properties(opt, False, False, value=value,
341                                         path=path,
342                                         force_permissive=force_permissive,
343                                         force_properties=force_properties,
344                                         self_properties=setting_properties)
345         if config_error is not None:
346             raise config_error
347         return value
348
349     def __setitem__(self, opt, value):  # pragma: optional cover
350         raise ConfigError(_('you should only set value with config'))
351
352     def setitem(self, opt, value, path, force_permissive=False,
353                 is_write=True):
354         # is_write is, for example, used with "force_store_value"
355         # user didn't change value, so not write
356         # valid opt
357         context = self._getcontext()
358         setting_properties = context.cfgimpl_get_settings()._getproperties(read_write=False)
359         if 'validator' in setting_properties:
360             fake_context = context._gen_fake_values()
361             fake_context.cfgimpl_get_values()._setitem(opt, value, path,
362                                                        force_permissive,
363                                                        is_write,
364                                                        setting_properties)
365             opt.impl_validate(value, fake_context)
366         self._setitem(opt, value, path, force_permissive, is_write,
367                       setting_properties, validate_properties=False)
368
369     def _setitem(self, opt, value, path, force_permissive, is_write,
370                  setting_properties, validate_properties=True):
371         if opt.impl_is_master_slaves():
372             opt.impl_get_master_slaves().setitem(self, opt, value, path)
373         self._setvalue(opt, path, value, force_permissive=force_permissive,
374                        is_write=is_write,
375                        setting_properties=setting_properties,
376                        validate_properties=validate_properties)
377
378     def _setvalue(self, opt, path, value, force_permissive=False,
379                   is_write=True, validate_properties=True,
380                   setting_properties=undefined):
381         context = self._getcontext()
382         context.cfgimpl_reset_cache()
383         if validate_properties:
384             setting = context.cfgimpl_get_settings()
385             setting.validate_properties(opt, False, is_write,
386                                         value=value, path=path,
387                                         force_permissive=force_permissive,
388                                         self_properties=setting_properties)
389         if isinstance(value, Multi):
390             value = list(value)
391             if opt.impl_is_submulti():
392                 for idx, val in enumerate(value):
393                     if isinstance(val, SubMulti):
394                         value[idx] = list(val)
395         owner = context.cfgimpl_get_settings().getowner()
396         self._p_.setvalue(path, value, owner)
397
398     def _is_meta(self, opt, path):
399         context = self._getcontext()
400         setting = context.cfgimpl_get_settings()
401         settings = setting._getproperties(opt, path, read_write=False)
402         if 'frozen' in settings and 'force_default_on_freeze' in settings:
403             return False
404         if self._p_.getowner(path, owners.default) is not owners.default:
405             return False
406         if context.cfgimpl_get_meta() is not None:
407             return True
408         return False
409
410     def getowner(self, opt, force_permissive=False):
411         """
412         retrieves the option's owner
413
414         :param opt: the `option.Option` object
415         :param force_permissive: behaves as if the permissive property
416                                  was present
417         :returns: a `setting.owners.Owner` object
418         """
419         if isinstance(opt, SymLinkOption) and \
420                 not isinstance(opt, DynSymLinkOption):
421             opt = opt._impl_getopt()
422         path = opt.impl_getpath(self._getcontext())
423         return self._getowner(opt, path, force_permissive=force_permissive)
424
425     def _getowner(self, opt, path, validate_properties=True,
426                   force_permissive=False, validate_meta=undefined,
427                   settings=undefined):
428         """get owner of an option
429         """
430         if not isinstance(opt, Option) and not isinstance(opt,
431                                                           DynSymLinkOption):
432             raise ConfigError(_('owner only avalaible for an option'))
433         context = self._getcontext()
434         if settings is undefined:
435             settings = context.cfgimpl_get_settings()._getproperties(
436                 opt, path, read_write=False)
437         if 'frozen' in settings and 'force_default_on_freeze' in settings:
438             return owners.default
439         if validate_properties:
440             self._getitem(opt, path, True, force_permissive, None, True,
441                           settings=settings)
442         owner = self._p_.getowner(path, owners.default)
443         if validate_meta is undefined:
444             if opt.impl_is_master_slaves('slave'):
445                 master = opt.impl_get_master_slaves().getmaster(opt)
446                 masterp = master.impl_getpath(context)
447                 validate_meta = self._is_meta(opt, masterp)
448             else:
449                 validate_meta = True
450         if validate_meta:
451             meta = context.cfgimpl_get_meta()
452             if owner is owners.default and meta is not None:
453                 owner = meta.cfgimpl_get_values()._getowner(opt, path)
454         return owner
455
456     def setowner(self, opt, owner):
457         """
458         sets a owner to an option
459
460         :param opt: the `option.Option` object
461         :param owner: a valid owner, that is a `setting.owners.Owner` object
462         """
463         if not isinstance(owner, owners.Owner):  # pragma: optional cover
464             raise TypeError(_("invalid generic owner {0}").format(str(owner)))
465
466         path = opt.impl_getpath(self._getcontext())
467         self._setowner(opt, path, owner)
468
469     def _setowner(self, opt, path, owner):
470         if not self._p_.hasvalue(path):  # pragma: optional cover
471             raise ConfigError(_('no value for {0} cannot change owner to {1}'
472                                 '').format(path, owner))
473         self._getcontext().cfgimpl_get_settings().validate_properties(opt,
474                                                                       False,
475                                                                       True,
476                                                                       path)
477
478         self._p_.setowner(path, owner)
479
480     def is_default_owner(self, opt, validate_properties=True,
481                          validate_meta=True):
482         """
483         :param config: *must* be only the **parent** config
484                        (not the toplevel config)
485         :return: boolean
486         """
487         path = opt.impl_getpath(self._getcontext())
488         return self._is_default_owner(opt, path,
489                                       validate_properties=validate_properties,
490                                       validate_meta=validate_meta)
491
492     def _is_default_owner(self, opt, path, validate_properties=True,
493                           validate_meta=True, settings=undefined):
494         return self._getowner(opt, path, validate_properties,
495                               validate_meta=validate_meta,
496                               settings=settings) == \
497             owners.default
498
499     def reset_cache(self, only_expired):
500         """
501         clears the cache if necessary
502         """
503         if only_expired:
504             self._p_.reset_expired_cache(int(time()))
505         else:
506             self._p_.reset_all_cache()
507
508     # information
509     def set_information(self, key, value):
510         """updates the information's attribute
511
512         :param key: information's key (ex: "help", "doc"
513         :param value: information's value (ex: "the help string")
514         """
515         self._p_.set_information(key, value)
516
517     def get_information(self, key, default=undefined):
518         """retrieves one information's item
519
520         :param key: the item string (ex: "help")
521         """
522         try:
523             return self._p_.get_information(key)
524         except ValueError:  # pragma: optional cover
525             if default is not undefined:
526                 return default
527             else:
528                 raise ValueError(_("information's item"
529                                    " not found: {0}").format(key))
530
531     def mandatory_warnings(self, force_permissive=False):
532         """convenience function to trace Options that are mandatory and
533         where no value has been set
534
535         :returns: generator of mandatory Option's path
536
537         """
538         def _mandatory_warnings(description):
539             #if value in cache, properties are not calculated
540             _ret = []
541             context = self._getcontext()
542             setting_properties = context.cfgimpl_get_settings()._getproperties(
543                 read_write=False)
544             for opt in description._impl_getchildren(context=context):
545                 if opt.impl_is_optiondescription():
546                     _ret.extend(_mandatory_warnings(opt))
547                 elif isinstance(opt, SymLinkOption) and \
548                         not isinstance(opt, DynSymLinkOption):
549                     pass
550                 else:
551                     path = opt.impl_getpath(self._getcontext())
552                     try:
553                         self._get_cached_item(opt, path=path,
554                                               force_properties=frozenset(('mandatory',)),
555                                               force_permissive=force_permissive,
556                                               setting_properties=setting_properties)
557                     except PropertiesOptionError as err:
558                         if err.proptype == ['mandatory']:
559                             _ret.append(path)
560             return _ret
561         self.reset_cache(False)
562         descr = self._getcontext().cfgimpl_get_description()
563         ret = _mandatory_warnings(descr)
564         self.reset_cache(False)
565         return ret
566
567     def force_cache(self):
568         """parse all option to force data in cache
569         """
570         context = self.context()
571         if not 'cache' in context.cfgimpl_get_settings():
572             raise ConfigError(_('can force cache only if cache '
573                                 'is actived in config'))
574         #remove all cached properties and value to update "expired" time
575         context.cfgimpl_reset_cache()
576         for path in context.cfgimpl_get_description().impl_getpaths(
577                 include_groups=True):
578             try:
579                 context.getattr(path)
580             except PropertiesOptionError:
581                 pass
582
583     def __getstate__(self):
584         return {'_p_': self._p_}
585
586     def _impl_setstate(self, storage):
587         self._p_._storage = storage
588
589     def __setstate__(self, states):
590         self._p_ = states['_p_']
591
592
593 # ____________________________________________________________
594 # multi types
595
596 class Multi(list):
597     """multi options values container
598     that support item notation for the values of multi options"""
599     __slots__ = ('opt', 'path', 'context', '__weakref__')
600
601     def __init__(self, value, context, opt, path):
602         """
603         :param value: the Multi wraps a list value
604         :param context: the home config that has the values
605         :param opt: the option object that have this Multi value
606         :param path: path of the option
607         """
608         if value is None:
609             value = []
610         if not opt.impl_is_submulti() and isinstance(value, Multi):  # pragma: optional cover
611             raise ValueError(_('{0} is already a Multi ').format(
612                 opt.impl_getname()))
613         self.opt = opt
614         self.path = path
615         if not isinstance(context, weakref.ReferenceType):  # pragma: optional cover
616             raise ValueError('context must be a Weakref')
617         self.context = context
618         if not isinstance(value, list):
619             if not '_index' in self.__slots__ and opt.impl_is_submulti():
620                 value = [[value]]
621             else:
622                 value = [value]
623         elif value != [] and not '_index' in self.__slots__ and \
624                 opt.impl_is_submulti() and not isinstance(value[0], list):
625             value = [value]
626         super(Multi, self).__init__(value)
627         if opt.impl_is_submulti():
628             if not '_index' in self.__slots__:
629                 for idx, val in enumerate(self):
630                     if not isinstance(val, SubMulti):
631                         super(Multi, self).__setitem__(idx, SubMulti(val,
632                                                                      context,
633                                                                      opt, path,
634                                                                      idx))
635                     self[idx].submulti = weakref.ref(self)
636
637     def _getcontext(self):
638         """context could be None, we need to test it
639         context is None only if all reference to `Config` object is deleted
640         (for example we delete a `Config` and we manipulate a reference to
641         old `SubConfig`, `Values`, `Multi` or `Settings`)
642         """
643         context = self.context()
644         if context is None:  # pragma: optional cover
645             raise ConfigError(_('the context does not exist anymore'))
646         return context
647
648     def __setitem__(self, index, value):
649         self._setitem(index, value)
650
651     def _setitem(self, index, value, validate=True):
652         context = self._getcontext()
653         setting = context.cfgimpl_get_settings()
654         setting_properties = setting._getproperties(read_write=False)
655         if 'validator' in setting_properties and validate:
656             fake_context = context._gen_fake_values()
657             fake_multi = fake_context.cfgimpl_get_values()._get_cached_item(
658                 self.opt, path=self.path, validate=False)
659             fake_multi._setitem(index, value, validate=False)
660             self._validate(value, fake_context, index, True)
661         #assume not checking mandatory property
662         super(Multi, self).__setitem__(index, value)
663         context.cfgimpl_get_values()._setvalue(self.opt, self.path, self)
664
665     #def __repr__(self, *args, **kwargs):
666     #    return super(Multi, self).__repr__(*args, **kwargs)
667
668     #def __getitem__(self, y):
669     #    return super(Multi, self).__getitem__(y)
670
671     def _get_validated_value(self, index):
672         values = self._getcontext().cfgimpl_get_values()
673         return values._get_validated_value(self.opt, self.path,
674                                            True, False, None, True,
675                                            index=index)
676
677     def append(self, value=undefined, force=False, setitem=True, validate=True):
678         """the list value can be updated (appened)
679         only if the option is a master
680         """
681         if not force and self.opt.impl_is_master_slaves('slave'):  # pragma: optional cover
682             raise SlaveError(_("cannot append a value on a multi option {0}"
683                                " which is a slave").format(self.opt.impl_getname()))
684         index = self.__len__()
685         if value is undefined:
686             try:
687                 value = self._get_validated_value(index)
688             except IndexError:
689                 value = None
690         context = self._getcontext()
691         setting = context.cfgimpl_get_settings()
692         setting_properties = setting._getproperties(read_write=False)
693         if 'validator' in setting_properties and validate and value not in [None, undefined]:
694             fake_context = context._gen_fake_values()
695             fake_multi = fake_context.cfgimpl_get_values()._get_cached_item(
696                 self.opt, path=self.path, validate=False)
697             fake_multi.append(value, validate=False, force=True)
698             self._validate(value, fake_context, index, True)
699         if not '_index' in self.__slots__ and self.opt.impl_is_submulti():
700             if not isinstance(value, SubMulti):
701                 value = SubMulti(value, self.context, self.opt, self.path, index)
702             value.submulti = weakref.ref(self)
703         super(Multi, self).append(value)
704         if setitem:
705             self._store(force)
706
707     def sort(self, cmp=None, key=None, reverse=False):
708         if self.opt.impl_is_master_slaves():
709             raise SlaveError(_("cannot sort multi option {0} if master or slave"
710                                "").format(self.opt.impl_getname()))
711         if sys.version_info[0] >= 3:
712             if cmp is not None:
713                 raise ValueError(_('cmp is not permitted in python v3 or '
714                                    'greater'))
715             super(Multi, self).sort(key=key, reverse=reverse)
716         else:
717             super(Multi, self).sort(cmp=cmp, key=key, reverse=reverse)
718         self._store()
719
720     def reverse(self):
721         if self.opt.impl_is_master_slaves():
722             raise SlaveError(_("cannot reverse multi option {0} if master or "
723                                "slave").format(self.opt.impl_getname()))
724         super(Multi, self).reverse()
725         self._store()
726
727     def insert(self, index, value, validate=True):
728         #FIXME value should be undefined
729         if self.opt.impl_is_master_slaves():
730             raise SlaveError(_("cannot insert multi option {0} if master or "
731                                "slave").format(self.opt.impl_getname()))
732         context = self._getcontext()
733         setting = setting = context.cfgimpl_get_settings()
734         setting_properties = setting._getproperties(read_write=False)
735         if 'validator' in setting_properties and validate and value is not None:
736             fake_context = context._gen_fake_values()
737             fake_multi = fake_context.cfgimpl_get_values()._get_cached_item(
738                 self.opt, path=self.path, validate=False)
739             fake_multi.insert(index, value, validate=False)
740             self._validate(value, fake_context, index, True)
741         super(Multi, self).insert(index, value)
742         self._store()
743
744     def extend(self, iterable, validate=True):
745         if self.opt.impl_is_master_slaves():
746             raise SlaveError(_("cannot extend multi option {0} if master or "
747                                "slave").format(self.opt.impl_getname()))
748         try:
749             index = self._index
750         except:
751             index = None
752         context = self._getcontext()
753         setting = context.cfgimpl_get_settings()
754         setting_properties = setting._getproperties(read_write=False)
755         if 'validator' in setting_properties and validate:
756             fake_context = context._gen_fake_values()
757             fake_multi = fake_context.cfgimpl_get_values()._get_cached_item(
758                 self.opt, path=self.path, validate=False)
759             fake_multi.extend(iterable, validate=False)
760             self._validate(iterable, fake_context, index)
761         super(Multi, self).extend(iterable)
762         self._store()
763
764     def _validate(self, value, fake_context, force_index, submulti=False):
765         self.opt.impl_validate(value, context=fake_context,
766                                force_index=force_index)
767
768     def pop(self, index, force=False):
769         """the list value can be updated (poped)
770         only if the option is a master
771
772         :param index: remove item a index
773         :type index: int
774         :param force: force pop item (withoud check master/slave)
775         :type force: boolean
776         :returns: item at index
777         """
778         context = self._getcontext()
779         if not force:
780             if self.opt.impl_is_master_slaves('slave'):  # pragma: optional cover
781                 raise SlaveError(_("cannot pop a value on a multi option {0}"
782                                    " which is a slave").format(self.opt.impl_getname()))
783             if self.opt.impl_is_master_slaves('master'):
784                 self.opt.impl_get_master_slaves().pop(self.opt,
785                                                       context.cfgimpl_get_values(), index)
786         #set value without valid properties
787         ret = super(Multi, self).pop(index)
788         self._store(force)
789         return ret
790
791     def _store(self, force=False):
792         self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path,
793                                                           self,
794                                                           validate_properties=not force)
795
796
797 class SubMulti(Multi):
798     __slots__ = ('_index', 'submulti')
799
800     def __init__(self, value, context, opt, path, index):
801         """
802         :param index: index (only for slave with submulti)
803         :type index: `int`
804         """
805         self._index = index
806         super(SubMulti, self).__init__(value, context, opt, path)
807
808     def append(self, value=undefined):
809         super(SubMulti, self).append(value, force=True)
810
811     def pop(self, index):
812         return super(SubMulti, self).pop(index, force=True)
813
814     def __setitem__(self, index, value):
815         self._setitem(index, value)
816
817     def _store(self, force=False):
818         #force is unused here
819         self._getcontext().cfgimpl_get_values()._setvalue(self.opt,
820                                                           self.path,
821                                                           self.submulti())
822
823     def _validate(self, value, fake_context, force_index, submulti=False):
824         if value is not None:
825             if submulti is False:
826                 super(SubMulti, self)._validate(value, fake_context,
827                                                 force_index, submulti)
828             else:
829                 self.opt.impl_validate(value, context=fake_context,
830                                        force_index=self._index,
831                                        force_submulti_index=force_index)
832
833     def _get_validated_value(self, index):
834         values = self._getcontext().cfgimpl_get_values()
835         return values._get_validated_value(self.opt, self.path,
836                                            True, False, None, True,
837                                            index=index,
838                                            submulti_index=self._index)