1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2014 Team tiramisu (see AUTHORS for all contributors)
4 # This program is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU Lesser General Public License as published by the
6 # Free Software Foundation, either version 3 of the License, or (at your
7 # option) any later version.
9 # This program is distributed in the hope that it will be useful, but WITHOUT
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
14 # You should have received a copy of the GNU Lesser General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 # The original `Config` design model is unproudly borrowed from
18 # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
19 # the whole pypy projet is under MIT licence
20 # ____________________________________________________________
22 from types import FunctionType
27 from ..setting import log, undefined, debug
28 from ..autolib import carry_out_calculation
29 from ..error import (ConfigError, ValueWarning, PropertiesOptionError,
31 from ..storage import get_storages_option
32 from . import MasterSlaves
35 StorageBase = get_storages_option('base')
37 name_regexp = re.compile(r'^[a-z][a-zA-Z\d\-_]*$')
38 forbidden_names = frozenset(['iter_all', 'iter_group', 'find', 'find_first',
39 'make_dict', 'unwrap_from_path', 'read_only',
40 'read_write', 'getowner', 'set_contexts'])
44 "an option's name is a str and does not start with 'impl' or 'cfgimpl'"
45 if not isinstance(name, str): # pragma: optional cover
47 if re.match(name_regexp, name) is not None and \
48 name not in forbidden_names and \
49 not name.startswith('impl_') and \
50 not name.startswith('cfgimpl_'):
52 else: # pragma: optional cover
56 def validate_callback(callback, callback_params, type_):
57 if type(callback) != FunctionType: # pragma: optional cover
58 raise ValueError(_('{0} must be a function').format(type_))
59 if callback_params is not None:
60 if not isinstance(callback_params, dict): # pragma: optional cover
61 raise ValueError(_('{0}_params must be a dict').format(type_))
62 for key, callbacks in callback_params.items():
63 if key != '' and len(callbacks) != 1: # pragma: optional cover
64 raise ValueError(_("{0}_params with key {1} mustn't have "
65 "length different to 1").format(type_,
67 if not isinstance(callbacks, tuple): # pragma: optional cover
68 raise ValueError(_('{0}_params must be tuple for key "{1}"'
70 for callbk in callbacks:
71 if isinstance(callbk, tuple):
73 if callbk != (None,): # pragma: optional cover
74 raise ValueError(_('{0}_params with length of '
75 'tuple as 1 must only have '
76 'None as first value'))
77 elif len(callbk) != 2: # pragma: optional cover
78 raise ValueError(_('{0}_params must only have 1 or 2 '
81 option, force_permissive = callbk
82 if not isinstance(option, Option) and not \
83 isinstance(option, SymLinkOption): # pragma: optional cover
84 raise ValueError(_('{0}_params must have an option'
85 ' not a {0} for first argument'
86 ).format(type_, type(option)))
87 if force_permissive not in [True, False]: # pragma: optional cover
88 raise ValueError(_('{0}_params must have a boolean'
89 ' not a {0} for second argument'
92 #____________________________________________________________
96 class Base(StorageBase):
99 def __init__(self, name, doc, default=None, default_multi=None,
100 requires=None, multi=False, callback=None,
101 callback_params=None, validator=None, validator_params=None,
102 properties=None, warnings_only=False, extra=None,
103 allow_empty_list=undefined, session=None):
104 if not valid_name(name): # pragma: optional cover
105 raise ValueError(_("invalid name: {0} for option").format(name))
106 if not multi and default_multi is not None: # pragma: optional cover
107 raise ValueError(_("default_multi is set whereas multi is False"
108 " in option: {0}").format(name))
115 elif multi is submulti:
119 raise ValueError(_('invalid multi value'))
120 if requires is not None:
121 calc_properties, requires = validate_requires_arg(is_multi,
124 calc_properties = frozenset()
126 if properties is None:
128 if not isinstance(properties, tuple): # pragma: optional cover
129 raise TypeError(_('invalid properties type {0} for {1},'
130 ' must be a tuple').format(
133 if validator is not None:
134 validate_callback(validator, validator_params, 'validator')
135 self._set_validator(validator, validator_params)
136 if calc_properties != frozenset([]) and properties is not tuple(): # pragma: optional cover
137 set_forbidden_properties = calc_properties & set(properties)
138 if set_forbidden_properties != frozenset():
139 raise ValueError('conflict: properties already set in '
140 'requirement {0}'.format(
141 list(set_forbidden_properties)))
143 session = self.getsession()
144 StorageBase.__init__(self, name, _multi, warnings_only, doc, extra,
145 calc_properties, requires, properties,
146 allow_empty_list, session=session)
147 if multi is not False and default is None:
149 err = self.impl_validate(default, is_multi=is_multi)
152 self._set_default_values(default, default_multi, is_multi)
153 ##callback is False in optiondescription
154 if callback is not False:
155 self.impl_set_callback(callback, callback_params, _init=True)
158 def impl_set_callback(self, callback, callback_params=None, _init=False):
159 if callback is None and callback_params is not None: # pragma: optional cover
160 raise ValueError(_("params defined for a callback function but "
161 "no callback defined"
162 " yet for option {0}").format(
163 self.impl_getname()))
164 if not _init and self.impl_get_callback()[0] is not None:
165 raise ConfigError(_("a callback is already set for option {0}, "
166 "cannot set another one's").format(self.impl_getname()))
167 self._validate_callback(callback, callback_params)
168 if callback is not None:
169 validate_callback(callback, callback_params, 'callback')
170 self._set_callback(callback, callback_params)
172 def impl_is_optiondescription(self):
173 return self.__class__.__name__ in ['OptionDescription',
174 'DynOptionDescription',
175 'SynDynOptionDescription']
177 def impl_is_dynoptiondescription(self):
178 return self.__class__.__name__ in ['DynOptionDescription',
179 'SynDynOptionDescription']
182 class BaseOption(Base):
183 """This abstract base class stands for attribute access
184 in options that have to be set only once, it is of course done in the
189 # ____________________________________________________________
191 def _impl_convert_requires(self, descr, load=False):
192 """export of the requires during the serialization process
194 :type descr: :class:`tiramisu.option.OptionDescription`
195 :param load: `True` if we are at the init of the option description
198 if not load and self.impl_getrequires() == []:
199 self._state_requires = None
200 elif load and self._state_requires is None:
201 del(self._state_requires)
204 _requires = self._state_requires
206 _requires = self.impl_getrequires()
208 for requires in _requires:
210 for require in requires:
212 new_require = [descr.impl_get_opt_by_path(require[0])]
214 new_require = [descr.impl_get_path_by_opt(require[0])]
215 new_require.extend(require[1:])
216 new_requires.append(tuple(new_require))
217 new_value.append(tuple(new_requires))
219 del(self._state_requires)
221 self._requires = new_value
223 self._state_requires = new_value
226 def _impl_getstate(self, descr):
227 """the under the hood stuff that need to be done
228 before the serialization.
230 :param descr: the parent :class:`tiramisu.option.OptionDescription`
233 for func in dir(self):
234 if func.startswith('_impl_convert_'):
235 getattr(self, func)(descr)
237 def __getstate__(self, stated=True):
238 """special method to enable the serialization with pickle
239 Usualy, a `__getstate__` method does'nt need any parameter,
240 but somme under the hood stuff need to be done before this action
242 :parameter stated: if stated is `True`, the serialization protocol
243 can be performed, not ready yet otherwise
244 :parameter type: bool
248 except AttributeError: # pragma: optional cover
249 raise SystemError(_('cannot serialize Option, '
250 'only in OptionDescription'))
251 if isinstance(self, SymLinkOption):
252 slots = frozenset(['_name', '_state_opt', '_stated'])
254 slots = self._impl_getattributes()
255 slots -= frozenset(['_cache_paths', '_cache_consistencies',
259 # remove variable if save variable converted
260 # in _state_xxxx variable
261 if '_state' + slot not in slots:
263 if slot.startswith('_state'):
264 states[slot] = getattr(self, slot)
265 # remove _state_xxx variable
266 self.__delattr__(slot)
268 states[slot] = getattr(self, slot)
269 except AttributeError:
272 del(states['_stated'])
276 def _impl_setstate(self, descr):
277 """the under the hood stuff that need to be done
278 before the serialization.
280 :type descr: :class:`tiramisu.option.OptionDescription`
282 for func in dir(self):
283 if func.startswith('_impl_convert_'):
284 getattr(self, func)(descr, load=True)
287 except AttributeError: # pragma: optional cover
290 def __setstate__(self, state):
291 """special method that enables us to serialize (pickle)
293 Usualy, a `__setstate__` method does'nt need any parameter,
294 but somme under the hood stuff need to be done before this action
296 :parameter state: a dict is passed to the loads, it is the attributes
297 of the options object
300 for key, value in state.items():
301 setattr(self, key, value)
303 def __setattr__(self, name, value):
304 """set once and only once some attributes in the option,
305 like `_name`. `_name` cannot be changed one the option and
306 pushed in the :class:`tiramisu.option.OptionDescription`.
308 if the attribute `_readonly` is set to `True`, the option is
309 "frozen" (which has noting to do with the high level "freeze"
310 propertie or "read_only" property)
312 if name != '_option' and \
313 not isinstance(value, tuple) and \
314 not name.startswith('_state') and \
315 not name == '_sa_instance_state':
317 # never change _name dans _opt
320 if self.impl_getname() is not None:
321 #so _name is already set
323 except (KeyError, AttributeError):
327 elif name != '_readonly':
328 is_readonly = self.impl_is_readonly()
329 if is_readonly: # pragma: optional cover
330 raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
331 " read-only").format(
332 self.__class__.__name__,
334 #self.impl_getname(),
336 super(BaseOption, self).__setattr__(name, value)
338 def impl_getpath(self, context):
339 return context.cfgimpl_get_description().impl_get_path_by_opt(self)
341 def impl_has_callback(self):
342 "to know if a callback has been defined or not"
343 return self.impl_get_callback()[0] is not None
345 def _is_subdyn(self):
346 return getattr(self, '_subdyn', None) is not None
348 def _impl_valid_unicode(self, value):
349 if sys.version_info[0] >= 3:
350 if not isinstance(value, str):
351 return ValueError(_('invalid string'))
353 if not isinstance(value, unicode) and not isinstance(value, str):
354 return ValueError(_('invalid unicode or string'))
357 class OnlyOption(BaseOption):
361 class Option(OnlyOption):
363 Abstract base class for configuration option's.
365 Reminder: an Option object is **not** a container for the value.
370 def _launch_consistency(self, current_opt, func, option, value, context,
371 index, submulti_index, all_cons_opts, warnings_only,
373 """Launch consistency now
375 :param func: function name, this name should start with _cons_
377 :param option: option that value is changing
378 :type option: `tiramisu.option.Option`
379 :param value: new value of this option
380 :param context: Config's context, if None, check default value instead
381 :type context: `tiramisu.config.Config`
382 :param index: only for multi option, consistency should be launch for
385 :param all_cons_opts: all options concerne by this consistency
386 :type all_cons_opts: `list` of `tiramisu.option.Option`
387 :param warnings_only: specific raise error for warning
388 :type warnings_only: `boolean`
389 :param transitive: propertyerror is transitive
390 :type transitive: `boolean`
392 if context is not undefined:
393 descr = context.cfgimpl_get_description()
396 val_consistencies = True
397 for opt in all_cons_opts:
399 if (isinstance(opt, DynSymLinkOption) and option._dyn == opt._dyn) or \
401 # option is current option
402 # we have already value, so use it
403 all_cons_vals.append(value)
405 #if context, calculate value, otherwise get default value
407 if context is not undefined:
408 if isinstance(opt, DynSymLinkOption):
409 path = opt.impl_getpath(context)
411 path = descr.impl_get_path_by_opt(opt)
412 opt_value = context.getattr(path, validate=False,
414 force_permissive=True,
416 if isinstance(opt_value, Exception):
417 if isinstance(opt_value, PropertiesOptionError):
419 log.debug('propertyerror in _launch_consistency: {0}'.format(opt_value))
427 opt_value = opt.impl_getdefault()
429 opt_value = opt.impl_getdefault()[index]
431 if self.impl_is_multi() and index is None:
432 # only check propertyerror for master/slaves is transitive
433 val_consistencies = False
434 all_cons_vals.append(opt_value)
436 if val_consistencies:
437 return getattr(self, func)(current_opt, all_cons_opts, all_cons_vals, warnings_only)
439 def impl_validate(self, value, context=undefined, validate=True,
440 force_index=None, force_submulti_index=None,
441 current_opt=undefined, is_multi=None,
442 display_warnings=True):
444 :param value: the option's value
445 :param context: Config's context
446 :type context: :class:`tiramisu.config.Config`
447 :param validate: if true enables ``self._validator`` validation
448 :type validate: boolean
449 :param force_index: if multi, value has to be a list
450 not if force_index is not None
451 :type force_index: integer
452 :param force_submulti_index: if submulti, value has to be a list
453 not if force_submulti_index is not None
454 :type force_submulti_index: integer
458 if current_opt is undefined:
461 def calculation_validator(val):
462 validator, validator_params = self.impl_get_validator()
463 if validator is not None:
464 if validator_params != {}:
465 validator_params_ = {}
466 for val_param, values in validator_params.items():
467 validator_params_[val_param] = values
468 #inject value in calculation
469 if '' in validator_params_:
470 lst = list(validator_params_[''])
472 validator_params_[''] = tuple(lst)
474 validator_params_[''] = (val,)
476 validator_params_ = {'': (val,)}
477 # Raise ValueError if not valid
478 value = carry_out_calculation(current_opt, context=context,
480 callback_params=validator_params_,
482 if isinstance(value, Exception):
485 def do_validation(_value, _index, submulti_index):
487 error = warning = None
490 err = self._validate(_value, context, current_opt,
494 log.debug('do_validation: value: {0}, index: {1}, '
495 'submulti_index: {2}'.format(_value, _index,
498 err_msg = '{0}'.format(err)
499 name = self.impl_getdoc()
500 if name is None or name == '':
501 name = self.impl_getname()
503 msg = _('"{0}" is an invalid {1} for "{2}", {3}'
504 '').format(_value, self._display_name,
505 self.impl_get_display_name(), err_msg)
507 msg = _('"{0}" is an invalid {1} for "{2}"'
508 '').format(_value, self._display_name,
509 self.impl_get_display_name())
510 return ValueError(msg)
514 error = calculation_validator(_value)
516 error = self._second_level_validation(_value, self._is_warnings_only())
519 log.debug(_('do_validation for {0}: error in value').format(
520 self.impl_getname()), exc_info=True)
521 if self._is_warnings_only():
524 if error is None and warning is None:
525 # if context launch consistency validation
526 #if context is not undefined:
527 ret = self._valid_consistency(current_opt, _value, context,
528 _index, submulti_index)
530 if isinstance(ret, ValueWarning):
533 elif isinstance(ret, ValueError):
538 msg = _('attention, "{0}" could be an invalid {1} for "{2}", {3}').format(
539 _value, self._display_name, self.impl_get_display_name(), warning)
540 if context is undefined or 'warnings' in \
541 context.cfgimpl_get_settings():
542 warnings.warn_explicit(ValueWarning(msg, self),
544 self.__class__.__name__, 0)
546 err_msg = '{0}'.format(error)
548 msg = _('"{0}" is an invalid {1} for "{2}", {3}'
549 '').format(_value, self._display_name,
550 self.impl_get_display_name(), err_msg)
552 msg = _('"{0}" is an invalid {1} for "{2}"'
553 '').format(_value, self._display_name,
554 self.impl_get_display_name())
555 return ValueError(msg)
557 # generic calculation
558 #if context is not undefined:
559 # descr = context.cfgimpl_get_description()
562 is_multi = self.impl_is_multi()
564 return do_validation(value, None, None)
565 elif force_index is not None:
566 if self.impl_is_submulti() and force_submulti_index is None:
567 if not isinstance(value, list): # pragma: optional cover
568 raise ValueError(_("invalid value {0} for option {1} which"
569 " must be a list").format(
570 value, self.impl_getname()))
571 for idx, val in enumerate(value):
572 err = do_validation(val, force_index, idx)
576 return do_validation(value, force_index, force_submulti_index)
577 elif not isinstance(value, list): # pragma: optional cover
578 return ValueError(_("invalid value {0} for option {1} which "
579 "must be a list").format(value,
580 self.impl_getname()))
581 elif self.impl_is_submulti() and force_submulti_index is None:
582 for idx, val in enumerate(value):
583 if not isinstance(val, list): # pragma: optional cover
584 return ValueError(_("invalid value {0} for option {1} "
585 "which must be a list of list"
587 self.impl_getname()))
588 for slave_idx, slave_val in enumerate(val):
589 err = do_validation(slave_val, idx, slave_idx)
593 for idx, val in enumerate(value):
594 err = do_validation(val, idx, force_submulti_index)
598 return self._valid_consistency(current_opt, None, context,
601 def impl_is_dynsymlinkoption(self):
604 def impl_is_master_slaves(self, type_='both'):
607 master_slaves = self.impl_get_master_slaves()
608 if master_slaves is not None:
609 if type_ in ('both', 'master') and \
610 master_slaves.is_master(self):
612 if type_ in ('both', 'slave') and \
613 not master_slaves.is_master(self):
617 def impl_get_master_slaves(self):
618 masterslaves = self._get_master_slave()
619 if masterslaves is None:
621 if not isinstance(masterslaves, MasterSlaves):
622 return MasterSlaves(masterslaves)
625 def impl_getdoc(self):
626 "accesses the Option's doc"
627 return self.impl_get_information('doc')
629 def impl_get_display_name(self):
630 name = self.impl_getdoc()
631 if name is None or name == '':
632 name = self.impl_getname()
635 def _valid_consistencies(self, other_opts):
636 if self._is_subdyn():
637 dynod = self._impl_getsubdyn()
640 if self.impl_is_submulti():
641 raise ConfigError(_('cannot add consistency with submulti option'))
642 is_multi = self.impl_is_multi()
643 for opt in other_opts:
644 if opt.impl_is_submulti():
645 raise ConfigError(_('cannot add consistency with submulti option'))
646 if not isinstance(opt, Option): # pragma: optional cover
647 raise ConfigError(_('consistency must be set with an option'))
650 raise ConfigError(_('almost one option in consistency is '
651 'in a dynoptiondescription but not all'))
652 if dynod != opt._impl_getsubdyn():
653 raise ConfigError(_('option in consistency must be in same'
654 ' dynoptiondescription'))
655 dynod = opt._impl_getsubdyn()
656 elif dynod is not None:
657 raise ConfigError(_('almost one option in consistency is in a '
658 'dynoptiondescription but not all'))
659 if self is opt: # pragma: optional cover
660 raise ConfigError(_('cannot add consistency with itself'))
661 if is_multi != opt.impl_is_multi(): # pragma: optional cover
662 raise ConfigError(_('every options in consistency must be '
665 def impl_add_consistency(self, func, *other_opts, **params):
666 """Add consistency means that value will be validate with other_opts
669 :param func: function's name
671 :param other_opts: options used to validate value
672 :type other_opts: `list` of `tiramisu.option.Option`
673 :param params: extra params (warnings_only and transitive are allowed)
675 if self.impl_is_readonly(): # pragma: optional cover
676 raise AttributeError(_("'{0}' ({1}) cannot add consistency, option is"
677 " read-only").format(
678 self.__class__.__name__,
679 self.impl_getname()))
680 self._valid_consistencies(other_opts)
681 func = '_cons_{0}'.format(func)
682 if func not in dir(self):
683 raise ConfigError(_('consistency {0} not available for this option').format(func))
684 all_cons_opts = tuple([self] + list(other_opts))
685 unknown_params = set(params.keys()) - set(['warnings_only', 'transitive'])
686 if unknown_params != set():
687 raise ValueError(_('unknow parameter {0} in consistency').format(unknown_params))
688 self._add_consistency(func, all_cons_opts, params)
689 #validate default value when add consistency
690 err = self.impl_validate(self.impl_getdefault())
692 self._del_consistency()
695 def _valid_consistency(self, option, value, context, index, submulti_idx):
696 if context is not undefined:
697 descr = context.cfgimpl_get_description()
698 if descr._cache_consistencies is None:
700 #consistencies is something like [('_cons_not_equal', (opt1, opt2))]
701 if isinstance(option, DynSymLinkOption):
702 consistencies = descr._cache_consistencies.get(option._impl_getopt())
704 consistencies = descr._cache_consistencies.get(option)
706 consistencies = option._get_consistencies()
707 if consistencies is not None:
708 for func, all_cons_opts, params in consistencies:
709 warnings_only = params.get('warnings_only', False)
710 transitive = params.get('transitive', True)
711 #all_cons_opts[0] is the option where func is set
712 if isinstance(option, DynSymLinkOption):
713 subpath = '.'.join(option._dyn.split('.')[:-1])
714 namelen = len(option._impl_getopt().impl_getname())
715 suffix = option.impl_getname()[namelen:]
717 for opt in all_cons_opts:
718 name = opt.impl_getname() + suffix
719 path = subpath + '.' + name
720 opts.append(opt._impl_to_dyn(name, path))
723 err = opts[0]._launch_consistency(self, func, option, value,
724 context, index, submulti_idx,
729 return ValueWarning(str(err), option)
733 def _cons_not_equal(self, current_opt, opts, vals, warnings_only):
736 for idx_inf, val_inf in enumerate(vals):
737 for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
738 if val_inf == val_sup is not None:
739 for opt_ in [opts[idx_inf], opts[idx_inf + idx_sup + 1]]:
740 if opt_ == current_opt:
743 equal.add('"{}"'.format(opt_.impl_get_display_name()))
746 log.debug(_('_cons_not_equal: {} are not different').format(display_list(list(equal))))
749 msg = _('should be different from the value of {}')
751 msg = _('must be different from the value of {}')
752 return ValueError(msg.format(display_list(list(equal))))
755 msg = _('value for {} should be different')
757 msg = _('value for {} must be different')
758 return ValueError(msg.format(display_list(list(equal))))
760 # serialize/unserialize
761 def _impl_convert_consistencies(self, descr, load=False):
762 """during serialization process, many things have to be done.
763 one of them is the localisation of the options.
764 The paths are set once for all.
766 :type descr: :class:`tiramisu.option.OptionDescription`
767 :param load: `True` if we are at the init of the option description
770 if not load and self._get_consistencies() == ():
771 self._state_consistencies = None
772 elif load and self._state_consistencies is None:
773 del(self._state_consistencies)
776 consistencies = self._state_consistencies
778 consistencies = self._get_consistencies()
780 for consistency in consistencies:
782 for obj in consistency[1]:
784 values.append(descr.impl_get_opt_by_path(obj))
786 values.append(descr.impl_get_path_by_opt(obj))
787 new_value.append((consistency[0], tuple(values), consistency[2]))
789 del(self._state_consistencies)
790 for new_val in new_value:
791 self._add_consistency(new_val[0], new_val[1], new_val[2])
793 self._state_consistencies = new_value
795 def _second_level_validation(self, value, warnings_only):
798 def _impl_to_dyn(self, name, path):
799 return DynSymLinkOption(name, self, dyn=path)
801 def _validate_callback(self, callback, callback_params):
804 * {'': ((option, permissive),), 'ip': ((None,), (option, permissive))
808 default_multi = self.impl_getdefault_multi()
809 is_multi = self.impl_is_multi()
810 default = self.impl_getdefault()
811 if (not is_multi and (default is not None or default_multi is not None)) or \
812 (is_multi and (default != [] or default_multi is not None)): # pragma: optional cover
813 raise ValueError(_("default value not allowed if option: {0} "
814 "is calculated").format(self.impl_getname()))
817 def validate_requires_arg(multi, requires, name):
818 """check malformed requirements
819 and tranform dict to internal tuple
821 :param requires: have a look at the
822 :meth:`tiramisu.setting.Settings.apply_requires` method to
824 the description of the requires dictionary
829 # start parsing all requires given by user (has dict)
830 # transforme it to a tuple
831 for require in requires:
832 if not isinstance(require, dict): # pragma: optional cover
833 raise ValueError(_("malformed requirements type for option:"
834 " {0}, must be a dict").format(name))
835 valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
837 unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
838 if unknown_keys != frozenset(): # pragma: optional cover
839 raise ValueError(_('malformed requirements for option: {0}'
840 ' unknown keys {1}, must only '
844 # prepare all attributes
845 if 'option' not in require or 'expected' not in require or \
846 'action' not in require:
847 raise ValueError(_("malformed requirements for option: {0}"
848 " require must have option, expected and"
849 " action keys").format(name))
850 option = require['option']
851 expected = require['expected']
852 action = require['action']
853 if action == 'force_store_value': # pragma: optional cover
854 raise ValueError(_("malformed requirements for option: {0}"
855 " action cannot be force_store_value"
857 inverse = require.get('inverse', False)
858 if inverse not in [True, False]: # pragma: optional cover
859 raise ValueError(_('malformed requirements for option: {0}'
860 ' inverse must be boolean'))
861 transitive = require.get('transitive', True)
862 if transitive not in [True, False]: # pragma: optional cover
863 raise ValueError(_('malformed requirements for option: {0}'
864 ' transitive must be boolean'))
865 same_action = require.get('same_action', True)
866 if same_action not in [True, False]: # pragma: optional cover
867 raise ValueError(_('malformed requirements for option: {0}'
868 ' same_action must be boolean'))
870 if not isinstance(option, Option): # pragma: optional cover
871 raise ValueError(_('malformed requirements '
872 'must be an option in option {0}').format(name))
873 if not multi and option.impl_is_multi():
874 raise ValueError(_('malformed requirements '
875 'multi option must not set '
876 'as requires of non multi option {0}').format(name))
877 if expected is not None:
878 err = option._validate(expected)
880 raise ValueError(_('malformed requirements second argument '
881 'must be valid for option {0}'
882 ': {1}').format(name, err))
883 if action in config_action:
884 if inverse != config_action[action]: # pragma: optional cover
885 raise ValueError(_("inconsistency in action types"
887 " action: {1}").format(name, action))
889 config_action[action] = inverse
890 if action not in ret_requires:
891 ret_requires[action] = {}
892 if option not in ret_requires[action]:
893 ret_requires[action][option] = (option, [expected], action,
894 inverse, transitive, same_action)
896 ret_requires[action][option][1].append(expected)
897 # transform dict to tuple
899 for opt_requires in ret_requires.values():
901 for require in opt_requires.values():
902 ret_action.append((require[0], tuple(require[1]), require[2],
903 require[3], require[4], require[5]))
904 ret.append(tuple(ret_action))
905 return frozenset(config_action.keys()), tuple(ret)
908 class SymLinkOption(OnlyOption):
909 # __slots__ = ('_opt', '_state_opt')
911 def __init__(self, name, opt):
912 if not isinstance(opt, Option): # pragma: optional cover
913 raise ValueError(_('malformed symlinkoption '
915 'for symlink {0}').format(name))
916 session = self.getsession()
917 super(Base, self).__init__(name, undefined, undefined, undefined,
918 undefined, undefined, undefined, undefined,
919 undefined, opt, session=session)
922 def __getattr__(self, name, context=undefined):
923 if name in ('_opt', '_readonly', 'impl_getpath', '_name',
924 '_state_opt', '_impl_setopt'):
925 return object.__getattr__(self, name)
927 return getattr(self._impl_getopt(), name)
929 def _impl_getstate(self, descr):
931 self._state_opt = descr.impl_get_path_by_opt(self._impl_getopt())
933 def _impl_setstate(self, descr):
934 self._impl_setopt(descr.impl_get_opt_by_path(self._state_opt))
937 self._set_readonly(True)
939 def impl_get_information(self, key, default=undefined):
940 return self._impl_getopt().impl_get_information(key, default)
942 def impl_is_readonly(self):
945 def impl_getproperties(self):
946 return self._impl_getopt()._properties
948 def impl_get_callback(self):
949 return self._impl_getopt().impl_get_callback()
951 def impl_has_callback(self):
952 "to know if a callback has been defined or not"
953 return self._impl_getopt().impl_has_callback()
955 def impl_is_multi(self):
956 return self._impl_getopt().impl_is_multi()
958 def _is_subdyn(self):
959 return getattr(self._impl_getopt(), '_subdyn', None) is not None
961 def _get_consistencies(self):
965 class DynSymLinkOption(object):
966 __slots__ = ('_dyn', '_opt', '_name')
968 def __init__(self, name, opt, dyn):
973 def __getattr__(self, name, context=undefined):
974 if name in ('_opt', '_readonly', 'impl_getpath', '_name', '_state_opt'):
975 return object.__getattr__(self, name)
977 return getattr(self._impl_getopt(), name)
979 def impl_getname(self):
982 def _impl_getopt(self):
985 def impl_getsuffix(self):
986 return self._dyn.split('.')[-1][len(self._impl_getopt().impl_getname()):]
988 def impl_getpath(self, context):
989 path = self._impl_getopt().impl_getpath(context)
990 base_path = '.'.join(path.split('.')[:-2])
991 if self.impl_is_master_slaves() and base_path is not '':
992 base_path = base_path + self.impl_getsuffix()
996 return base_path + '.' + self._dyn
998 def impl_validate(self, value, context=undefined, validate=True,
999 force_index=None, force_submulti_index=None, is_multi=None,
1000 display_warnings=True):
1001 return self._impl_getopt().impl_validate(value, context, validate,
1003 force_submulti_index,
1006 display_warnings=display_warnings)
1008 def impl_is_dynsymlinkoption(self):