remove try/except
[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 .error import ConfigError, SlaveError, PropertiesOptionError
22 from .setting import owners, expires_time, undefined
23 from .autolib import carry_out_calculation
24 from .i18n import _
25 from .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 _get_multi(self, opt, path):
58         return Multi([], self.context, opt, path)
59
60     def _getdefaultvalue(self, opt, path, with_meta, index, submulti_index,
61                          returns_raise):
62         # if value has callback and is not set
63         if opt.impl_has_callback():
64             callback, callback_params = opt.impl_get_callback()
65             value = carry_out_calculation(opt, context=self._getcontext(),
66                                           callback=callback,
67                                           callback_params=callback_params,
68                                           index=index,
69                                           returns_raise=returns_raise)
70             if isinstance(value, list) and index is not None:
71                 #if return a list and index is set, return value only if
72                 #it's a submulti without submulti_index and without list of list
73                 if opt.impl_is_submulti() and submulti_index is undefined and \
74                         (len(value) == 0 or not isinstance(value[0], list)):
75                     return value
76             else:
77                 return value
78         if with_meta:
79             meta = self._getcontext().cfgimpl_get_meta()
80             if meta is not None:
81                 value = meta.cfgimpl_get_values(
82                 )._get_cached_value(opt, path, index=index, submulti_index=submulti_index,
83                                     from_masterslave=True, returns_raise=True)
84                 if isinstance(value, Exception):
85                     if not isinstance(value, PropertiesOptionError):
86                         raise value
87                 else:
88                     if isinstance(value, Multi):
89                         if index is not None:
90                             value = value[index]
91                         else:
92                             value = list(value)
93                     return value
94         # now try to get default value
95         value = opt.impl_getdefault()
96         if opt.impl_is_multi() and index is not None:
97             if value == []:
98                 value = opt.impl_getdefault_multi()
99             else:
100                 if len(value) > index:
101                     value = value[index]
102                 else:
103                     value = opt.impl_getdefault_multi()
104         return value
105
106     def _getvalue(self, opt, path, is_default, self_properties,
107                   index=undefined, submulti_index=undefined, with_meta=True,
108                   masterlen=undefined, returns_raise=False):
109         """actually retrieves the value
110
111         :param opt: the `option.Option()` object
112         :returns: the option's value (or the default value if not set)
113         """
114         force_default = 'frozen' in self_properties and \
115             'force_default_on_freeze' in self_properties
116         # not default value
117         if not is_default and not force_default:
118             if opt.impl_is_master_slaves('slave'):
119                 return self._p_.getvalue(path, index)
120             else:
121                 value = self._p_.getvalue(path)
122                 if index is not None:
123                     if len(value) > index:
124                         return value[index]
125                     #value is smaller than expected
126                     #so return default value
127                 else:
128                     return value
129         return self._getdefaultvalue(opt, path, with_meta, index, submulti_index, returns_raise)
130
131     def get_modified_values(self):
132         context = self._getcontext()
133         if context._impl_descr is not None:
134             for opt, path in context.cfgimpl_get_description(
135             )._get_force_store_value():
136                 self._getowner(opt, path, force_permissive=True)
137         return self._p_.get_modified_values()
138
139     def __contains__(self, opt):
140         """
141         implements the 'in' keyword syntax in order provide a pythonic way
142         to kow if an option have a value
143
144         :param opt: the `option.Option()` object
145         """
146         path = opt.impl_getpath(self._getcontext())
147         return self._contains(path)
148
149     def _contains(self, path):
150         return self._p_.hasvalue(path)
151
152     def __delitem__(self, opt):
153         """overrides the builtins `del()` instructions"""
154         self.reset(opt)
155
156     def reset(self, opt, path=None, validate=True, _setting_properties=None):
157         context = self._getcontext()
158         setting = context.cfgimpl_get_settings()
159         if path is None:
160             path = opt.impl_getpath(context)
161         if _setting_properties is None:
162             _setting_properties = setting._getproperties(read_write=False)
163         hasvalue = self._contains(path)
164
165         if validate and hasvalue and 'validator' in _setting_properties:
166             fake_context = context._gen_fake_values()
167             fake_value = fake_context.cfgimpl_get_values()
168             fake_value.reset(opt, path, validate=False)
169             fake_value._get_cached_value(opt, path,
170                                          setting_properties=_setting_properties,
171                                          check_frozen=True)
172         if opt.impl_is_master_slaves('master'):
173             opt.impl_get_master_slaves().reset(opt, self, _setting_properties)
174         if hasvalue:
175             self._p_.resetvalue(path)
176             context.cfgimpl_reset_cache()
177
178     def _isempty(self, opt, value, force_allow_empty_list=False, index=None):
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 index in [None, undefined] and 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_value(opt, validate=validate,
208                                       force_permissive=force_permissive)
209
210     def _get_cached_value(self, opt, path=None, validate=True,
211                           force_permissive=False, trusted_cached_properties=True,
212                           validate_properties=True,
213                           setting_properties=undefined, self_properties=undefined,
214                           index=None, submulti_index=undefined, from_masterslave=False,
215                           with_meta=True, masterlen=undefined, check_frozen=False,
216                           returns_raise=False):
217         context = self._getcontext()
218         settings = context.cfgimpl_get_settings()
219         if path is None:
220             path = opt.impl_getpath(context)
221         ntime = None
222         if setting_properties is undefined:
223             setting_properties = settings._getproperties(read_write=False)
224         if self_properties is undefined:
225             self_properties = settings._getproperties(opt, path,
226                                                       read_write=False,
227                                                       setting_properties=setting_properties,
228                                                       index=index)
229         if 'cache' in setting_properties and self._p_.hascache(path, index):
230             if 'expire' in setting_properties:
231                 ntime = int(time())
232             is_cached, value = self._p_.getcache(path, ntime, index)
233             if is_cached:
234                 if opt.impl_is_multi() and not isinstance(value, Multi) and index is None:
235                     value = Multi(value, self.context, opt, path)
236                 if not trusted_cached_properties:
237                     # revalidate properties (because of not default properties)
238                     props = settings.validate_properties(opt, False, False, value=value,
239                                                          path=path,
240                                                          force_permissive=force_permissive,
241                                                          setting_properties=setting_properties,
242                                                          self_properties=self_properties,
243                                                          index=index)
244                     if props:
245                         if returns_raise:
246                             return props
247                         else:
248                             raise props
249                 return value
250         if not from_masterslave and opt.impl_is_master_slaves():
251             val = opt.impl_get_master_slaves().getitem(self, opt, path,
252                                                        validate,
253                                                        force_permissive,
254                                                        trusted_cached_properties,
255                                                        validate_properties,
256                                                        setting_properties=setting_properties,
257                                                        index=index,
258                                                        self_properties=self_properties,
259                                                        returns_raise=returns_raise)
260         else:
261             val = self._get_validated_value(opt, path, validate,
262                                             force_permissive,
263                                             validate_properties,
264                                             setting_properties=setting_properties,
265                                             self_properties=self_properties,
266                                             with_meta=with_meta,
267                                             masterlen=masterlen,
268                                             index=index,
269                                             submulti_index=submulti_index,
270                                             check_frozen=check_frozen,
271                                             returns_raise=returns_raise)
272         if isinstance(val, Exception):
273             if returns_raise:
274                 return val
275             else:
276                 raise val
277         # cache doesn't work with SubMulti yet
278         if not isinstance(val, SubMulti) and 'cache' in setting_properties and \
279                 validate and validate_properties and force_permissive is False \
280                 and trusted_cached_properties is True:
281             if 'expire' in setting_properties:
282                 if ntime is None:
283                     ntime = int(time())
284                 ntime = ntime + expires_time
285             self._p_.setcache(path, val, ntime, index)
286         return val
287
288     def _get_validated_value(self, opt, path, validate, force_permissive,
289                              validate_properties,
290                              index=None, submulti_index=undefined,
291                              with_meta=True, setting_properties=undefined,
292                              self_properties=undefined, masterlen=undefined,
293                              check_frozen=False, returns_raise=False):
294         """same has getitem but don't touch the cache
295         index is None for slave value, if value returned is not a list, just return []
296         """
297         context = self._getcontext()
298         setting = context.cfgimpl_get_settings()
299         if setting_properties is undefined:
300             setting_properties = setting._getproperties(read_write=False)
301         if self_properties is undefined:
302             self_properties = setting._getproperties(opt, path, read_write=False, index=index)
303         is_default = self._is_default_owner(opt, path,
304                                             validate_properties=False,
305                                             validate_meta=False,
306                                             self_properties=self_properties,
307                                             index=index)
308         config_error = None
309         value = self._getvalue(opt, path, is_default, self_properties,
310                                index=index, submulti_index=submulti_index,
311                                with_meta=with_meta,
312                                masterlen=masterlen,
313                                returns_raise=True)
314         if isinstance(value, Exception):
315             if isinstance(value, ConfigError):
316                 # For calculating properties, we need value (ie for mandatory
317                 # value).
318                 # If value is calculating with a PropertiesOptionError's option
319                 # _getvalue raise a ConfigError.
320                 # We can not raise ConfigError if this option should raise
321                 # PropertiesOptionError too. So we get config_error and raise
322                 # ConfigError if properties did not raise.
323                 config_error = value
324                 # value is not set, for 'undefined' (cannot set None because of
325                 # mandatory property)
326                 value = undefined
327             else:
328                 raise value
329         else:
330             if index is undefined:
331                 force_index = None
332             else:
333                 force_index = index
334             if opt.impl_is_multi():
335                 if force_index is None:
336                     value = Multi(value, self.context, opt, path)
337                 elif opt.impl_is_submulti() and submulti_index is undefined:
338                     value = SubMulti(value, self.context, opt, path,
339                                      force_index)
340
341             if validate:
342                 if submulti_index is undefined:
343                     force_submulti_index = None
344                 else:
345                     force_submulti_index = submulti_index
346                 err = opt.impl_validate(value, context,
347                                         'validator' in setting_properties,
348                                         force_index=force_index,
349                                         force_submulti_index=force_submulti_index)
350                 if err:
351                     config_error = err
352                     value = None
353
354             if is_default and 'force_store_value' in self_properties:
355                 if isinstance(value, Multi):
356                     item = list(value)
357                 else:
358                     item = value
359                 self.setitem(opt, item, path, check_frozen=False,
360                              force_permissive=force_permissive)
361         if validate_properties:
362             if config_error is not None:
363                 # should not raise PropertiesOptionError if option is
364                 # mandatory
365                 val_props = undefined
366             else:
367                 val_props = value
368             props = setting.validate_properties(opt, False, check_frozen, value=val_props,
369                                                 path=path,
370                                                 force_permissive=force_permissive,
371                                                 setting_properties=setting_properties,
372                                                 self_properties=self_properties,
373                                                 index=index)
374             if props:
375                 if returns_raise:
376                     return props
377                 else:
378                     raise props
379         if config_error is not None:
380             if returns_raise:
381                 return config_error
382             else:
383                 raise config_error
384         return value
385
386     def __setitem__(self, opt, value):  # pragma: optional cover
387         raise ConfigError(_('you should only set value with config'))
388
389     def setitem(self, opt, value, path, force_permissive=False,
390                 check_frozen=True, not_raises=False):
391         # check_frozen is, for example, used with "force_store_value"
392         # user didn't change value, so not write
393         # valid opt
394         context = self._getcontext()
395         setting_properties = context.cfgimpl_get_settings()._getproperties(read_write=False)
396         if 'validator' in setting_properties:
397             fake_context = context._gen_fake_values()
398             fake_values = fake_context.cfgimpl_get_values()
399             fake_values._setvalue(opt, path, value)
400             props = fake_values.validate(opt, value, path, check_frozen,
401                                          force_permissive, setting_properties,
402                                          not_raises=not_raises)
403             if props and not_raises:
404                 return
405             err = opt.impl_validate(value, fake_context)
406             if err:
407                 raise err
408         self._setvalue(opt, path, value)
409
410     def _setvalue(self, opt, path, value):
411         context = self._getcontext()
412         context.cfgimpl_reset_cache()
413         owner = context.cfgimpl_get_settings().getowner()
414         # in storage, value must not be a multi
415         if isinstance(value, Multi):
416             value = list(value)
417             if opt.impl_is_submulti():
418                 for idx, val in enumerate(value):
419                     if isinstance(val, SubMulti):
420                         value[idx] = list(val)
421         #FIXME pourquoi là et pas dans masterslaves ??
422         if opt.impl_is_master_slaves('slave'):
423             self._p_.resetvalue(path)
424             for idx, val in enumerate(value):
425                 self._p_.setvalue(path, val, owner, idx)
426         else:
427             self._p_.setvalue(path, value, owner, None)
428
429     def validate(self, opt, value, path, check_frozen=True, force_permissive=False,
430                  setting_properties=undefined, valid_masterslave=True,
431                  not_raises=False, returns_raise=False):
432         if valid_masterslave and opt.impl_is_master_slaves():
433             opt.impl_get_master_slaves().validate(self, opt, value, path, returns_raise)
434         props = self._getcontext().cfgimpl_get_settings().validate_properties(opt,
435                                                                               False,
436                                                                               check_frozen,
437                                                                               value=value,
438                                                                               path=path,
439                                                                               force_permissive=force_permissive,
440                                                                               setting_properties=setting_properties)
441         if props:
442             if not_raises:
443                 return props
444             raise props
445
446     def _is_meta(self, opt, path):
447         context = self._getcontext()
448         setting = context.cfgimpl_get_settings()
449         self_properties = setting._getproperties(opt, path, read_write=False)
450         if 'frozen' in self_properties and 'force_default_on_freeze' in self_properties:
451             return False
452         if self._p_.getowner(path, owners.default, only_default=True) is not owners.default:
453             return False
454         if context.cfgimpl_get_meta() is not None:
455             return True
456         return False
457
458     def getowner(self, opt, index=None, force_permissive=False):
459         """
460         retrieves the option's owner
461
462         :param opt: the `option.Option` object
463         :param force_permissive: behaves as if the permissive property
464                                  was present
465         :returns: a `setting.owners.Owner` object
466         """
467         if isinstance(opt, SymLinkOption) and \
468                 not isinstance(opt, DynSymLinkOption):
469             opt = opt._impl_getopt()
470         path = opt.impl_getpath(self._getcontext())
471         return self._getowner(opt, path, index=index, force_permissive=force_permissive)
472
473     def _getowner(self, opt, path, validate_properties=True,
474                   force_permissive=False, validate_meta=undefined,
475                   self_properties=undefined, only_default=False,
476                   index=None):
477         """get owner of an option
478         """
479         if not isinstance(opt, Option) and not isinstance(opt,
480                                                           DynSymLinkOption):
481             raise ConfigError(_('owner only avalaible for an option'))
482         context = self._getcontext()
483         if self_properties is undefined:
484             self_properties = context.cfgimpl_get_settings()._getproperties(
485                 opt, path, read_write=False)
486         if 'frozen' in self_properties and 'force_default_on_freeze' in self_properties:
487             return owners.default
488         if validate_properties:
489             self._get_cached_value(opt, path, True, force_permissive, None, True,
490                                    self_properties=self_properties)
491         owner = self._p_.getowner(path, owners.default, only_default=only_default, index=index)
492         if validate_meta is undefined:
493             if opt.impl_is_master_slaves('slave'):
494                 master = opt.impl_get_master_slaves().getmaster(opt)
495                 masterp = master.impl_getpath(context)
496                 validate_meta = self._is_meta(opt, masterp)
497             else:
498                 validate_meta = True
499         if validate_meta:
500             meta = context.cfgimpl_get_meta()
501             if owner is owners.default and meta is not None:
502                 owner = meta.cfgimpl_get_values()._getowner(opt, path,
503                                                             validate_properties=validate_properties,
504                                                             force_permissive=force_permissive,
505                                                             self_properties=self_properties,
506                                                             only_default=only_default, index=index)
507         return owner
508
509     def setowner(self, opt, owner):
510         """
511         sets a owner to an option
512
513         :param opt: the `option.Option` object
514         :param owner: a valid owner, that is a `setting.owners.Owner` object
515         """
516         if not isinstance(owner, owners.Owner):  # pragma: optional cover
517             raise TypeError(_("invalid generic owner {0}").format(str(owner)))
518
519         path = opt.impl_getpath(self._getcontext())
520         if not self._p_.hasvalue(path):  # pragma: optional cover
521             raise ConfigError(_('no value for {0} cannot change owner to {1}'
522                                 '').format(path, owner))
523         props = self._getcontext().cfgimpl_get_settings().validate_properties(opt,
524                                                                               False,
525                                                                               True,
526                                                                               path)
527         if props:
528             raise props
529         self._p_.setowner(path, owner)
530
531     def is_default_owner(self, opt, validate_properties=True,
532                          validate_meta=True, index=None):
533         """
534         :param config: *must* be only the **parent** config
535                        (not the toplevel config)
536         :return: boolean
537         """
538         path = opt.impl_getpath(self._getcontext())
539         return self._is_default_owner(opt, path,
540                                       validate_properties=validate_properties,
541                                       validate_meta=validate_meta, index=index)
542
543     def _is_default_owner(self, opt, path, validate_properties=True,
544                           validate_meta=True, self_properties=undefined,
545                           index=None):
546         if not opt.impl_is_master_slaves('slave'):
547             index = None
548         d = self._getowner(opt, path, validate_properties,
549                            validate_meta=validate_meta,
550                            self_properties=self_properties, only_default=True,
551                            index=index)
552         return d == owners.default
553
554     def reset_cache(self, only_expired):
555         """
556         clears the cache if necessary
557         """
558         if only_expired:
559             self._p_.reset_expired_cache(int(time()))
560         else:
561             self._p_.reset_all_cache()
562
563     # information
564     def set_information(self, key, value):
565         """updates the information's attribute
566
567         :param key: information's key (ex: "help", "doc"
568         :param value: information's value (ex: "the help string")
569         """
570         self._p_.set_information(key, value)
571
572     def get_information(self, key, default=undefined):
573         """retrieves one information's item
574
575         :param key: the item string (ex: "help")
576         """
577         return self._p_.get_information(key, default)
578
579     def mandatory_warnings(self, force_permissive=False, validate=True):
580         """convenience function to trace Options that are mandatory and
581         where no value has been set
582
583         :param force_permissive: do raise with permissives properties
584         :type force_permissive: `bool`
585         :param validate: validate value when calculating properties
586         :type validate: `bool`
587
588         :returns: generator of mandatory Option's path
589         """
590         context = self._getcontext()
591         settings = context.cfgimpl_get_settings()
592         setting_properties = context.cfgimpl_get_settings()._getproperties()
593         setting_properties.add('mandatory')
594
595         def _mandatory_warnings(description, currpath=None):
596             if currpath is None:
597                 currpath = []
598             for opt in description._impl_getchildren(context=context):
599                 name = opt.impl_getname()
600                 path = '.'.join(currpath + [name])
601
602                 if opt.impl_is_optiondescription():
603                     if not settings.validate_properties(opt, True, False, path=path,
604                                                         force_permissive=force_permissive,
605                                                         setting_properties=setting_properties):
606                         for path in _mandatory_warnings(opt, currpath + [name]):
607                             yield path
608                 else:
609                     if isinstance(opt, SymLinkOption) and \
610                             not isinstance(opt, DynSymLinkOption):
611                         true_opt = opt._impl_getopt()
612                         true_path = descr.impl_get_path_by_opt(true_opt)
613                     else:
614                         true_opt = opt
615                         true_path = path
616                     self_properties = settings._getproperties(true_opt, true_path,
617                                                               read_write=False,
618                                                               setting_properties=setting_properties)
619                     if 'mandatory' in self_properties:
620                         err = self._get_cached_value(true_opt, path=true_path,
621                                                      trusted_cached_properties=False,
622                                                      force_permissive=force_permissive,
623                                                      setting_properties=setting_properties,
624                                                      self_properties=self_properties,
625                                                      validate=validate, returns_raise=True)
626                         if not isinstance(err, Exception):
627                             pass
628                         elif isinstance(err, PropertiesOptionError):
629                             if err.proptype == ['mandatory']:
630                                 yield path
631                         elif isinstance(err, ConfigError):
632                             if validate:
633                                 raise err
634                             else:
635                                 #assume that uncalculated value is an empty value
636                                 yield path
637                         else:
638                             raise err
639
640         descr = self._getcontext().cfgimpl_get_description()
641         for path in _mandatory_warnings(descr):
642             yield path
643
644     def force_cache(self):
645         """parse all option to force data in cache
646         """
647         context = self.context()
648         if not 'cache' in context.cfgimpl_get_settings():
649             raise ConfigError(_('can force cache only if cache '
650                                 'is actived in config'))
651         #remove all cached properties and value to update "expired" time
652         context.cfgimpl_reset_cache()
653         for path in context.cfgimpl_get_description().impl_getpaths(
654                 include_groups=True):
655             err = context.getattr(path, returns_raise=True)
656             if isinstance(err, Exception) and not isinstance(err, PropertiesOptionError):
657                 raise err
658
659     def __getstate__(self):
660         return {'_p_': self._p_}
661
662     def _impl_setstate(self, storage):
663         self._p_._storage = storage
664
665     def __setstate__(self, states):
666         self._p_ = states['_p_']
667
668
669 # ____________________________________________________________
670 # multi types
671 class Multi(list):
672     """multi options values container
673     that support item notation for the values of multi options"""
674     __slots__ = ('opt', 'path', 'context', '__weakref__')
675
676     def __init__(self, value, context, opt, path):
677         """
678         :param value: the Multi wraps a list value
679         :param context: the home config that has the values
680         :param opt: the option object that have this Multi value
681         :param path: path of the option
682         """
683         if value is None:
684             value = []
685         if not opt.impl_is_submulti() and isinstance(value, Multi):  # pragma: optional cover
686             raise ValueError(_('{0} is already a Multi ').format(
687                 opt.impl_getname()))
688         self.opt = opt
689         self.path = path
690         if not isinstance(context, weakref.ReferenceType):  # pragma: optional cover
691             raise ValueError('context must be a Weakref')
692         self.context = context
693         if not isinstance(value, list):
694             if not '_index' in self.__slots__ and opt.impl_is_submulti():
695                 value = [[value]]
696             else:
697                 value = [value]
698         elif value != [] and not '_index' in self.__slots__ and \
699                 opt.impl_is_submulti() and not isinstance(value[0], list):
700             value = [value]
701         super(Multi, self).__init__(value)
702         if opt.impl_is_submulti():
703             if not '_index' in self.__slots__:
704                 for idx, val in enumerate(self):
705                     if not isinstance(val, SubMulti):
706                         super(Multi, self).__setitem__(idx, SubMulti(val,
707                                                                      context,
708                                                                      opt, path,
709                                                                      idx))
710                     self[idx].submulti = weakref.ref(self)
711
712     def _getcontext(self):
713         """context could be None, we need to test it
714         context is None only if all reference to `Config` object is deleted
715         (for example we delete a `Config` and we manipulate a reference to
716         old `SubConfig`, `Values`, `Multi` or `Settings`)
717         """
718         context = self.context()
719         if context is None:  # pragma: optional cover
720             raise ConfigError(_('the context does not exist anymore'))
721         return context
722
723     def __setitem__(self, index, value):
724         self._setitem(index, value)
725
726     def _setitem(self, index, value, validate=True):
727         context = self._getcontext()
728         setting = context.cfgimpl_get_settings()
729         setting_properties = setting._getproperties(read_write=False)
730         if 'validator' in setting_properties and validate:
731             fake_context = context._gen_fake_values()
732             fake_multi = fake_context.cfgimpl_get_values()._get_cached_value(
733                 self.opt, path=self.path, validate=False)
734             fake_multi._setitem(index, value, validate=False)
735             self._validate(value, fake_context, index, True)
736         #assume not checking mandatory property
737         super(Multi, self).__setitem__(index, value)
738         self._store()
739
740     #def __repr__(self, *args, **kwargs):
741     #    return super(Multi, self).__repr__(*args, **kwargs)
742
743     def __getitem__(self, index):
744         value = super(Multi, self).__getitem__(index)
745         if isinstance(value, PropertiesOptionError):
746             raise value
747         return value
748
749     def _get_validated_value(self, index):
750         values = self._getcontext().cfgimpl_get_values()
751         return values._get_validated_value(self.opt, self.path,
752                                            True, False, True,
753                                            index=index)
754
755     def append(self, value=undefined, force=False, setitem=True, validate=True):
756         """the list value can be updated (appened)
757         only if the option is a master
758         """
759         if not force and self.opt.impl_is_master_slaves('slave'):  # pragma: optional cover
760             raise SlaveError(_("cannot append a value on a multi option {0}"
761                                " which is a slave").format(self.opt.impl_getname()))
762         index = self.__len__()
763         if value is undefined:
764             value = self._get_validated_value(index)
765         if validate and value not in [None, undefined]:
766             context = self._getcontext()
767             setting = context.cfgimpl_get_settings()
768             setting_properties = setting._getproperties(read_write=False)
769             if 'validator' in setting_properties:
770                 fake_context = context._gen_fake_values()
771                 fake_multi = fake_context.cfgimpl_get_values()._get_cached_value(
772                     self.opt, path=self.path, validate=False)
773                 fake_multi.append(value, validate=False, force=True)
774                 self._validate(value, fake_context, index, True)
775         if not '_index' in self.__slots__ and self.opt.impl_is_submulti():
776             if not isinstance(value, SubMulti):
777                 value = SubMulti(value, self.context, self.opt, self.path, index)
778             value.submulti = weakref.ref(self)
779         super(Multi, self).append(value)
780         if setitem:
781             self._store(force=force)
782
783     def append_properties_error(self, err):
784         super(Multi, self).append(err)
785
786     def sort(self, cmp=None, key=None, reverse=False):
787         if self.opt.impl_is_master_slaves():
788             raise SlaveError(_("cannot sort multi option {0} if master or slave"
789                                "").format(self.opt.impl_getname()))
790         if sys.version_info[0] >= 3:
791             if cmp is not None:
792                 raise ValueError(_('cmp is not permitted in python v3 or '
793                                    'greater'))
794             super(Multi, self).sort(key=key, reverse=reverse)
795         else:
796             super(Multi, self).sort(cmp=cmp, key=key, reverse=reverse)
797         self._store()
798
799     def reverse(self):
800         if self.opt.impl_is_master_slaves():
801             raise SlaveError(_("cannot reverse multi option {0} if master or "
802                                "slave").format(self.opt.impl_getname()))
803         super(Multi, self).reverse()
804         self._store()
805
806     def insert(self, index, value, validate=True):
807         if self.opt.impl_is_master_slaves():
808             raise SlaveError(_("cannot insert multi option {0} if master or "
809                                "slave").format(self.opt.impl_getname()))
810         context = self._getcontext()
811         setting = setting = context.cfgimpl_get_settings()
812         setting_properties = setting._getproperties(read_write=False)
813         if 'validator' in setting_properties and validate and value is not None:
814             fake_context = context._gen_fake_values()
815             fake_multi = fake_context.cfgimpl_get_values()._get_cached_value(
816                 self.opt, path=self.path, validate=False)
817             fake_multi.insert(index, value, validate=False)
818             self._validate(value, fake_context, index, True)
819         super(Multi, self).insert(index, value)
820         self._store()
821
822     def extend(self, iterable, validate=True):
823         if self.opt.impl_is_master_slaves():
824             raise SlaveError(_("cannot extend multi option {0} if master or "
825                                "slave").format(self.opt.impl_getname()))
826         index = getattr(self, '_index', None)
827         context = self._getcontext()
828         setting = context.cfgimpl_get_settings()
829         setting_properties = setting._getproperties(read_write=False)
830         if 'validator' in setting_properties and validate:
831             fake_context = context._gen_fake_values()
832             fake_multi = fake_context.cfgimpl_get_values()._get_cached_value(
833                 self.opt, path=self.path, validate=False)
834             fake_multi.extend(iterable, validate=False)
835             self._validate(iterable, fake_context, index)
836         super(Multi, self).extend(iterable)
837         self._store()
838
839     def _validate(self, value, fake_context, force_index, submulti=False):
840         err = self.opt.impl_validate(value, context=fake_context,
841                                      force_index=force_index)
842         if err:
843             raise err
844
845     def pop(self, index, force=False):
846         """the list value can be updated (poped)
847         only if the option is a master
848
849         :param index: remove item a index
850         :type index: int
851         :param force: force pop item (withoud check master/slave)
852         :type force: boolean
853         :returns: item at index
854         """
855         context = self._getcontext()
856         if not force:
857             if self.opt.impl_is_master_slaves('slave'):  # pragma: optional cover
858                 raise SlaveError(_("cannot pop a value on a multi option {0}"
859                                    " which is a slave").format(self.opt.impl_getname()))
860             if self.opt.impl_is_master_slaves('master'):
861                 self.opt.impl_get_master_slaves().pop(self.opt,
862                                                       context.cfgimpl_get_values(), index)
863         #set value without valid properties
864         ret = super(Multi, self).pop(index)
865         self._store(force=force)
866         return ret
867
868     def _store(self, force=False):
869         values = self._getcontext().cfgimpl_get_values()
870         if not force:
871             #FIXME could get properties an pass it
872             values.validate(self.opt, self, self.path,
873                             valid_masterslave=False)
874         values._setvalue(self.opt, self.path, self)
875
876
877 class SubMulti(Multi):
878     __slots__ = ('_index', 'submulti')
879
880     def __init__(self, value, context, opt, path, index):
881         """
882         :param index: index (only for slave with submulti)
883         :type index: `int`
884         """
885         self._index = index
886         super(SubMulti, self).__init__(value, context, opt, path)
887
888     def append(self, value=undefined):
889         super(SubMulti, self).append(value, force=True)
890
891     def pop(self, index):
892         return super(SubMulti, self).pop(index, force=True)
893
894     def __setitem__(self, index, value):
895         self._setitem(index, value)
896
897     def _store(self, force=False):
898         #force is unused here
899         values = self._getcontext().cfgimpl_get_values()
900         values.validate(self.opt, self, self.path, valid_masterslave=False)
901         values._setvalue(self.opt, self.path, self.submulti())
902
903     def _validate(self, value, fake_context, force_index, submulti=False):
904         if value is not None:
905             if submulti is False:
906                 super(SubMulti, self)._validate(value, fake_context,
907                                                 force_index, submulti)
908             else:
909                 err = self.opt.impl_validate(value, context=fake_context,
910                                              force_index=self._index,
911                                              force_submulti_index=force_index)
912                 if err:
913                     raise err
914
915     def _get_validated_value(self, index):
916         values = self._getcontext().cfgimpl_get_values()
917         return values._get_validated_value(self.opt, self.path,
918                                            True, False, True,
919                                            index=index,
920                                            submulti_index=self._index)