1 # -*- coding: utf-8 -*-
2 "option types and option description"
3 # Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors)
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 # The original `Config` design model is unproudly borrowed from
20 # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
21 # the whole pypy projet is under MIT licence
22 # ____________________________________________________________
26 from types import FunctionType
29 #from pickle import loads, dumps
31 from sqlalchemy.ext.declarative import declarative_base
32 from sqlalchemy import create_engine, Column, Integer, String, Boolean, \
33 PickleType, ForeignKey, Table
34 from sqlalchemy.orm import relationship, backref
35 from sqlalchemy.orm import sessionmaker
37 from tiramisu.error import ConfigError, ConflictError, ValueWarning
38 from tiramisu.setting import groups, multitypes
39 from tiramisu.i18n import _
40 from tiramisu.autolib import carry_out_calculation
42 name_regexp = re.compile(r'^\d+')
43 forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
44 'make_dict', 'unwrap_from_path', 'read_only',
45 'read_write', 'getowner', 'set_contexts')
49 "an option's name is a str and does not start with 'impl' or 'cfgimpl'"
50 if not isinstance(name, str):
52 if re.match(name_regexp, name) is None and not name.startswith('_') \
53 and name not in forbidden_names \
54 and not name.startswith('impl_') \
55 and not name.startswith('cfgimpl_'):
59 #____________________________________________________________
62 engine = create_engine('sqlite:///:memory:')
63 Base = declarative_base()
66 class _RequireExpected(Base):
67 __tablename__ = 'expected'
68 id = Column(Integer, primary_key=True)
69 expected = Column(PickleType)
70 require = Column(Integer, ForeignKey('require.id'))
72 def __init__(self, expected):
73 self.expected = expected
76 class _RequireOption(Base):
77 __tablename__ = 'require'
78 id = Column(Integer, primary_key=True)
79 option = Column(Integer, ForeignKey('baseoption.id'))
80 r_opt = Column(Integer)
81 expected = relationship("_RequireExpected")
82 action = Column(String, nullable=False)
83 inverse = Column(Boolean, default=False)
84 transitive = Column(Boolean, default=True)
85 same_action = Column(Boolean, default=True)
87 def __init__(self, option, expected, action, inverse, transitive,
89 self.r_opt = option.id
90 for expect in expected:
91 self.expected.append(_RequireExpected(expect))
93 self.inverse = inverse
94 self.transitive = transitive
95 self.same_action = same_action
97 def get_expected(self):
98 for expected in self.expected:
99 yield(expected.expected)
101 def get_option(self, config):
102 return config.cfgimpl_get_description().impl_get_opt_by_id(self.r_opt)
105 property_table = Table('property', Base.metadata,
106 Column('left_id', Integer, ForeignKey('propertyoption.name')),
107 Column('right_id', Integer, ForeignKey('baseoption.id'))
111 class _PropertyOption(Base):
112 __tablename__ = 'propertyoption'
113 name = Column(String, primary_key=True)
115 def __init__(self, name):
119 class _Information(Base):
120 __tablename__ = 'information'
121 id = Column(Integer, primary_key=True)
122 option = Column(Integer, ForeignKey('baseoption.id'))
124 value = Column(PickleType)
126 def __init__(self, key, value):
131 class _CallbackParamOption(Base):
132 __tablename__ = 'callback_param_option'
133 id = Column(Integer, primary_key=True)
134 callback_param = Column(Integer, ForeignKey('callback_param.id'))
135 option = Column(Integer)
136 force_permissive = Column(Boolean)
137 value = Column(PickleType)
139 def __init__(self, option=None, force_permissive=None, value=None):
140 if value is not None:
143 if isinstance(option, SymLinkOption):
145 self.option = option.id
146 self.force_permissive = force_permissive
148 def get_option(self, config):
149 return config.cfgimpl_get_description().impl_get_opt_by_id(self.option)
152 class _CallbackParam(Base):
153 __tablename__ = 'callback_param'
154 id = Column(Integer, primary_key=True)
155 callback = Column(Integer, ForeignKey('baseoption.id'))
156 name = Column(String)
157 params = relationship('_CallbackParamOption')
159 def __init__(self, name, params):
162 if isinstance(param, tuple):
163 self.params.append(_CallbackParamOption(option=param[0],
164 force_permissive=param[1]))
166 self.params.append(_CallbackParamOption(value=param))
169 consistency_table = Table('consistencyopt', Base.metadata,
170 Column('left_id', Integer, ForeignKey('consistency.id')),
171 Column('right_id', Integer, ForeignKey('baseoption.id'))
175 class _Consistency(Base):
176 __tablename__ = 'consistency'
177 id = Column(Integer, primary_key=True)
178 func = Column(PickleType)
180 def __init__(self, func, all_cons_opts):
182 for option in all_cons_opts:
183 option._consistencies.append(self)
186 class BaseOption(Base):
187 """This abstract base class stands for attribute access
188 in options that have to be set only once, it is of course done in the
191 __tablename__ = 'baseoption'
192 id = Column(Integer, primary_key=True)
193 _name = Column(String)
194 _informations = relationship('_Information')
195 _default = Column(PickleType)
196 _default_multi = Column(PickleType)
197 _requires = relationship('_RequireOption')
198 _multi = Column(Boolean)
199 _multitype = Column(String)
200 _callback = Column(PickleType)
201 _callback_params = relationship('_CallbackParam')
202 _validator = Column(PickleType)
203 _validator_params = relationship('_CallbackParam')
204 _parent = Column(Integer, ForeignKey('baseoption.id'))
205 _children = relationship('BaseOption', enable_typechecks=False)
206 _properties = relationship('_PropertyOption', secondary=property_table,
207 backref=backref('options', enable_typechecks=False))
208 _warnings_only = Column(Boolean)
209 _readonly = Column(Boolean, default=False)
210 _consistencies = relationship('_Consistency', secondary=consistency_table,
211 backref=backref('options', enable_typechecks=False))
212 _choice_values = Column(PickleType)
213 _choice_open_values = Column(Boolean)
214 _type = Column(String(50))
216 'polymorphic_identity': 'person',
217 'polymorphic_on': _type
219 #FIXME devrait etre une table
220 _optiondescription_group_type = Column(String)
221 #__slots__ = ('_name', '_requires', '_properties', '_readonly',
222 # '_calc_properties', '_impl_informations',
223 # '_state_readonly', '_state_requires', '_stated')
225 def __init__(self, name, doc, default=None, default_multi=None,
226 requires=None, multi=False, callback=None,
227 callback_params=None, validator=None, validator_params=None,
228 properties=None, warnings_only=False, choice_values=None,
229 choice_open_values=None):
230 if not valid_name(name):
231 raise ValueError(_("invalid name: {0} for option").format(name))
233 self.impl_set_information('doc', doc)
234 requires = validate_requires_arg(requires, self._name)
235 if requires is not None:
236 for values in requires.values():
237 for require in values.values():
238 self._requires.append(_RequireOption(*require))
239 if not multi and default_multi is not None:
240 raise ValueError(_("a default_multi is set whereas multi is False"
241 " in option: {0}").format(name))
242 if default_multi is not None:
244 self._validate(default_multi)
245 except ValueError as err:
246 raise ValueError(_("invalid default_multi value {0} "
247 "for option {1}: {2}").format(
248 str(default_multi), name, err))
253 self._multitype = multitypes.default
254 self._default_multi = default_multi
255 if callback is not None and ((not multi and (default is not None or
256 default_multi is not None))
257 or (multi and (default != [] or
258 default_multi is not None))
260 raise ValueError(_("default value not allowed if option: {0} "
261 "is calculated").format(name))
262 if properties is None:
264 if not isinstance(properties, tuple):
265 raise TypeError(_('invalid properties type {0} for {1},'
266 ' must be a tuple').format(
269 if validator is not None:
270 validate_callback(validator, validator_params, 'validator')
271 self._validator = validator
272 if validator_params is not None:
273 for key, values in validator_params.items():
274 self._validator_params.append(_CallbackParam(key, values))
275 if callback is None and callback_params is not None:
276 raise ValueError(_("params defined for a callback function but "
277 "no callback defined"
278 " yet for option {0}").format(name))
279 if callback is not None:
280 validate_callback(callback, callback_params, 'callback')
281 self._callback = callback
282 if callback_params is not None:
283 for key, values in callback_params.items():
284 self._callback_params.append(_CallbackParam(key, values))
285 if requires is not None and properties is not tuple():
286 set_forbidden_properties = set(properties) & set(requires.keys())
287 if set_forbidden_properties != frozenset():
288 raise ValueError('conflict: properties already set in '
289 'requirement {0}'.format(
290 list(set_forbidden_properties)))
291 if choice_values is not None:
292 self._choice_values = choice_values
293 if choice_open_values is not None:
294 self._choice_open_values = choice_open_values
295 self.impl_validate(default)
296 if multi and default is None:
299 self._default = default
300 for prop in properties:
301 prop_obj = session.query(_PropertyOption).filter(_PropertyOption.name == prop).first()
303 prop_obj = _PropertyOption(prop)
304 self._properties.append(prop_obj)
305 self._warnings_only = warnings_only
307 # ____________________________________________________________
309 def impl_set_information(self, key, value):
310 """updates the information's attribute
311 (which is a dictionary)
313 :param key: information's key (ex: "help", "doc"
314 :param value: information's value (ex: "the help string")
316 #FIXME pas append ! remplacer !
317 info = session.query(_Information).filter_by(option=self.id, key=key).first()
319 self._informations.append(_Information(key, value))
323 def impl_get_information(self, key, default=None):
324 """retrieves one information's item
326 :param key: the item string (ex: "help")
328 info = session.query(_Information).filter_by(option=self.id, key=key).first()
331 elif default is not None:
334 raise ValueError(_("information's item not found: {0}").format(
337 # ____________________________________________________________
339 def _impl_convert_requires(self, descr, load=False):
340 """export of the requires during the serialization process
342 :type descr: :class:`tiramisu.option.OptionDescription`
343 :param load: `True` if we are at the init of the option description
346 if not load and self._requires is None:
347 self._state_requires = None
348 elif load and self._state_requires is None:
349 self._requires = None
350 del(self._state_requires)
353 _requires = self._state_requires
355 _requires = self._requires
357 for requires in _requires:
359 for require in requires:
361 new_require = [descr.impl_get_opt_by_path(require[0])]
363 new_require = [descr.impl_get_path_by_opt(require[0])]
364 new_require.extend(require[1:])
365 new_requires.append(tuple(new_require))
366 new_value.append(tuple(new_requires))
368 del(self._state_requires)
369 self._requires = new_value
371 self._state_requires = new_value
374 def _impl_getstate(self, descr):
375 """the under the hood stuff that need to be done
376 before the serialization.
378 :param descr: the parent :class:`tiramisu.option.OptionDescription`
381 for func in dir(self):
382 if func.startswith('_impl_convert_'):
383 getattr(self, func)(descr)
384 self._state_readonly = self._readonly
386 def __getstate__(self, stated=True):
387 """special method to enable the serialization with pickle
388 Usualy, a `__getstate__` method does'nt need any parameter,
389 but somme under the hood stuff need to be done before this action
391 :parameter stated: if stated is `True`, the serialization protocol
392 can be performed, not ready yet otherwise
393 :parameter type: bool
397 except AttributeError:
398 raise SystemError(_('cannot serialize Option, '
399 'only in OptionDescription'))
401 for subclass in self.__class__.__mro__:
402 if subclass is not object:
403 slots.update(subclass.__slots__)
404 slots -= frozenset(['_cache_paths', '_cache_consistencies',
408 # remove variable if save variable converted
409 # in _state_xxxx variable
410 if '_state' + slot not in slots:
411 if slot.startswith('_state'):
413 states[slot] = getattr(self, slot)
414 # remove _state_xxx variable
415 self.__delattr__(slot)
418 states[slot] = getattr(self, slot)
419 except AttributeError:
422 del(states['_stated'])
426 def _impl_setstate(self, descr):
427 """the under the hood stuff that need to be done
428 before the serialization.
430 :type descr: :class:`tiramisu.option.OptionDescription`
432 for func in dir(self):
433 if func.startswith('_impl_convert_'):
434 getattr(self, func)(descr, load=True)
436 self._readonly = self._state_readonly
437 del(self._state_readonly)
439 except AttributeError:
442 def __setstate__(self, state):
443 """special method that enables us to serialize (pickle)
445 Usualy, a `__setstate__` method does'nt need any parameter,
446 but somme under the hood stuff need to be done before this action
448 :parameter state: a dict is passed to the loads, it is the attributes
449 of the options object
452 for key, value in state.items():
453 setattr(self, key, value)
455 def impl_getname(self):
459 class Option(BaseOption):
461 Abstract base class for configuration option's.
463 Reminder: an Option object is **not** a container for the value.
465 # __slots__ = ('_multi', '_validator', '_default_multi', '_default',
466 # '_state_callback', '_callback', '_multitype',
467 # '_consistencies', '_warnings_only', '_master_slaves',
468 # '_state_consistencies', '__weakref__')
471 def __init__(self, name, doc, default=None, default_multi=None,
472 requires=None, multi=False, callback=None,
473 callback_params=None, validator=None, validator_params=None,
474 properties=None, warnings_only=False, choice_values=None,
475 choice_open_values=None):
477 :param name: the option's name
478 :param doc: the option's description
479 :param default: specifies the default value of the option,
480 for a multi : ['bla', 'bla', 'bla']
481 :param default_multi: 'bla' (used in case of a reset to default only at
483 :param requires: is a list of names of options located anywhere
484 in the configuration.
485 :param multi: if true, the option's value is a list
486 :param callback: the name of a function. If set, the function's output
487 is responsible of the option's value
488 :param callback_params: the callback's parameter
489 :param validator: the name of a function which stands for a custom
490 validation of the value
491 :param validator_params: the validator's parameters
492 :param properties: tuple of default properties
493 :param warnings_only: _validator and _consistencies don't raise if True
494 Values()._warning contain message
497 super(Option, self).__init__(name, doc, default, default_multi,
498 requires, multi, callback,
499 callback_params, validator,
500 validator_params, properties,
501 warnings_only, choice_values,
506 #def __setattr__(self, name, value):
507 # """set once and only once some attributes in the option,
508 # like `_name`. `_name` cannot be changed one the option and
509 # pushed in the :class:`tiramisu.option.OptionDescription`.
511 # if the attribute `_readonly` is set to `True`, the option is
512 # "frozen" (which has noting to do with the high level "freeze"
513 # propertie or "read_only" property)
515 # #FIXME ne devrait pas pouvoir redefinir _option
516 # if not name == '_option':
517 # is_readonly = False
518 # # never change _name
519 # if name == '_name':
522 # #so _name is already set
526 # elif name != '_readonly':
528 # if self._readonly is True:
530 # except AttributeError:
531 # self._readonly = False
533 # raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
534 # " read-only").format(
535 # self.__class__.__name__,
538 # object.__setattr__(self, name, value)
540 def impl_getproperties(self):
541 for prop in self._properties:
544 def impl_getrequires(self):
545 return self._requires
547 def _launch_consistency(self, func, option, value, context, index,
549 """Launch consistency now
551 :param func: function name, this name should start with _cons_
553 :param option: option that value is changing
554 :type option: `tiramisu.option.Option`
555 :param value: new value of this option
556 :param context: Config's context, if None, check default value instead
557 :type context: `tiramisu.config.Config`
558 :param index: only for multi option, consistency should be launch for
561 :param all_cons_opts: all options concerne by this consistency
562 :type all_cons_opts: `list` of `tiramisu.option.Option`
564 if context is not None:
565 descr = context.cfgimpl_get_description()
566 #option is also in all_cons_opts
567 if option not in all_cons_opts:
568 raise ConfigError(_('option not in all_cons_opts'))
571 for opt in all_cons_opts:
576 #if context, calculate value, otherwise get default value
577 if context is not None:
578 opt_value = context._getattr(
579 descr.impl_get_path_by_opt(opt), validate=False)
581 opt_value = opt.impl_getdefault()
584 if not self.impl_is_multi() or option == opt:
585 all_cons_vals.append(opt_value)
587 #value is not already set, could be higher index
589 all_cons_vals.append(opt_value[index])
591 #so return if no value
593 getattr(self, func)(all_cons_opts, all_cons_vals)
595 def impl_validate(self, value, context=None, validate=True,
598 :param value: the option's value
599 :param context: Config's context
600 :type context: :class:`tiramisu.config.Config`
601 :param validate: if true enables ``self._validator`` validation
602 :type validate: boolean
603 :param force_no_multi: if multi, value has to be a list
604 not if force_no_multi is True
605 :type force_no_multi: boolean
610 def val_validator(val):
611 if self._validator is not None:
612 class FakeCallbackParamOption(object):
613 def __init__(self, option=None,
614 force_permissive=None,
617 self.force_permissive = force_permissive
620 class FakeCallbackParam(object):
621 def __init__(self, key, params):
624 if self._validator_params != []:
625 validator_params = []
626 validator_params_option = [FakeCallbackParamOption(value=val)]
627 #if '' in self._validator_params:
628 # for param in self._validator_params['']:
629 # if isinstance(param, tuple):
630 # validator_params_option.append(FakeCallbackParamOption(option=param[0], force_permissive=param[1]))
632 # validator_params_option.append(FakeCallbackParamOption(value=param))
633 validator_params.append(FakeCallbackParam('', validator_params_option))
634 for key in self._validator_params:
636 validator_params.append(key)
638 validator_params = (FakeCallbackParam('', (FakeCallbackParamOption(value=val),)),)
639 # Raise ValueError if not valid
640 carry_out_calculation(self, config=context,
641 callback=self._validator,
642 callback_params=validator_params)
644 def do_validation(_value, _index=None):
649 self._validate(_value)
650 except ValueError as err:
651 raise ValueError(_('invalid value for option {0}: {1}'
652 '').format(self.impl_getname(), err))
654 # valid with self._validator
655 val_validator(_value)
656 # if not context launch consistency validation
657 if context is not None:
658 descr._valid_consistency(self, _value, context, _index)
659 self._second_level_validation(_value)
660 except ValueError as err:
661 msg = _("invalid value for option {0}: {1}").format(
662 self.impl_getname(), err)
663 if self._warnings_only:
664 warnings.warn_explicit(ValueWarning(msg, self),
666 self.__class__.__name__, 0)
668 raise ValueError(msg)
670 # generic calculation
671 if context is not None:
672 descr = context.cfgimpl_get_description()
674 if not self._multi or force_index is not None:
675 do_validation(value, force_index)
677 if not isinstance(value, list):
678 raise ValueError(_("invalid value {0} for option {1} which must be a list").format(value, self.impl_getname()))
679 for index, val in enumerate(value):
680 do_validation(val, index)
682 def impl_getdefault(self):
683 "accessing the default value"
686 def impl_getdefault_multi(self):
687 "accessing the default value for a multi"
688 return self._default_multi
690 def impl_get_multitype(self):
691 return self._multitype
693 def impl_get_master_slaves(self):
694 return self._master_slaves
696 def impl_is_empty_by_default(self):
697 "no default value has been set yet"
698 if ((not self.impl_is_multi() and self._default is None) or
699 (self.impl_is_multi() and (self._default == []
700 or None in self._default))):
704 def impl_getdoc(self):
705 "accesses the Option's doc"
706 return self.impl_get_information('doc')
708 def impl_has_callback(self):
709 "to know if a callback has been defined or not"
710 if self._callback is None:
715 def impl_get_callback(self):
716 return self._callback, self._callback_params
718 #def impl_getkey(self, value):
721 def impl_is_multi(self):
724 def impl_add_consistency(self, func, *other_opts):
725 """Add consistency means that value will be validate with other_opts
728 :param func: function's name
730 :param other_opts: options used to validate value
731 :type other_opts: `list` of `tiramisu.option.Option`
733 for opt in other_opts:
734 if not isinstance(opt, Option):
735 raise ConfigError(_('consistency should be set with an option'))
737 raise ConfigError(_('cannot add consistency with itself'))
738 if self.impl_is_multi() != opt.impl_is_multi():
739 raise ConfigError(_('every options in consistency should be '
741 func = '_cons_{0}'.format(func)
742 all_cons_opts = tuple([self] + list(other_opts))
743 value = self.impl_getdefault()
744 if value is not None:
745 if self.impl_is_multi():
746 for idx, val in enumerate(value):
747 self._launch_consistency(func, self, val, None,
750 self._launch_consistency(func, self, value, None,
752 _Consistency(func, all_cons_opts)
753 self.impl_validate(self.impl_getdefault())
755 def _cons_not_equal(self, opts, vals):
756 for idx_inf, val_inf in enumerate(vals):
757 for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
758 if val_inf == val_sup is not None:
759 raise ValueError(_("same value for {0} and {1}").format(
760 opts[idx_inf].impl_getname(), opts[idx_inf + idx_sup + 1].impl_getname()))
762 def _impl_convert_callbacks(self, descr, load=False):
764 if not load and self._callback is None:
765 self._state_callback = None
766 elif load and self._state_callback is None:
767 self._callback = None
768 del(self._state_callback)
771 callback, callback_params = self._state_callback
773 callback, callback_params = self._callback
774 if callback_params is not None:
776 for key, values in callback_params.items():
779 if isinstance(value, tuple):
781 value = (descr.impl_get_opt_by_path(value[0]),
784 value = (descr.impl_get_path_by_opt(value[0]),
787 cllbck_prms[key] = tuple(vls)
792 del(self._state_callback)
793 self._callback = (callback, cllbck_prms)
795 self._state_callback = (callback, cllbck_prms)
797 # serialize/unserialize
798 def _impl_convert_consistencies(self, descr, load=False):
799 """during serialization process, many things have to be done.
800 one of them is the localisation of the options.
801 The paths are set once for all.
803 :type descr: :class:`tiramisu.option.OptionDescription`
804 :param load: `True` if we are at the init of the option description
807 if not load and self._consistencies is None:
808 self._state_consistencies = None
809 elif load and self._state_consistencies is None:
810 self._consistencies = None
811 del(self._state_consistencies)
814 consistencies = self._state_consistencies
816 consistencies = self._consistencies
817 if isinstance(consistencies, list):
819 for consistency in consistencies:
821 for obj in consistency[1]:
823 values.append(descr.impl_get_opt_by_path(obj))
825 values.append(descr.impl_get_path_by_opt(obj))
826 new_value.append((consistency[0], tuple(values)))
830 for key, _consistencies in consistencies.items():
832 for key_cons, _cons in _consistencies:
837 descr.impl_get_opt_by_path(_con))
840 descr.impl_get_path_by_opt(_con))
841 new_value[key].append((key_cons, tuple(_list_cons)))
843 del(self._state_consistencies)
844 self._consistencies = new_value
846 self._state_consistencies = new_value
848 def _second_level_validation(self, value):
852 class ChoiceOption(Option):
853 """represents a choice out of several objects.
855 The option can also have the value ``None``
858 #__slots__ = ('_values', '_open_values')
860 'polymorphic_identity': 'choice',
863 def __init__(self, name, doc, values, default=None, default_multi=None,
864 requires=None, multi=False, callback=None,
865 callback_params=None, open_values=False, validator=None,
866 validator_params=None, properties=None, warnings_only=False):
868 :param values: is a list of values the option can possibly take
870 if not isinstance(values, tuple):
871 raise TypeError(_('values must be a tuple for {0}').format(name))
872 if open_values not in (True, False):
873 raise TypeError(_('open_values must be a boolean for '
875 super(ChoiceOption, self).__init__(name, doc, default=default,
876 default_multi=default_multi,
878 callback_params=callback_params,
882 validator_params=validator_params,
883 properties=properties,
884 warnings_only=warnings_only,
885 choice_values=values,
886 choice_open_values=open_values)
888 def impl_get_values(self):
889 return self._choice_values
891 def impl_is_openvalues(self):
892 return self._choice_open_values
894 def _validate(self, value):
895 if not self._choice_open_values and not value in self._choice_values:
896 raise ValueError(_('value {0} is not permitted, '
897 'only {1} is allowed'
898 '').format(value, self._choice_values))
901 class BoolOption(Option):
902 "represents a choice between ``True`` and ``False``"
903 # __slots__ = tuple()
905 'polymorphic_identity': 'bool',
908 def _validate(self, value):
909 if not isinstance(value, bool):
910 raise ValueError(_('invalid boolean'))
913 class IntOption(Option):
914 "represents a choice of an integer"
915 # __slots__ = tuple()
917 'polymorphic_identity': 'int',
920 def _validate(self, value):
921 if not isinstance(value, int):
922 raise ValueError(_('invalid integer'))
925 class FloatOption(Option):
926 "represents a choice of a floating point number"
929 'polymorphic_identity': 'float',
932 def _validate(self, value):
933 if not isinstance(value, float):
934 raise ValueError(_('invalid float'))
937 class StrOption(Option):
938 "represents the choice of a string"
941 'polymorphic_identity': 'string',
944 def _validate(self, value):
945 if not isinstance(value, str):
946 raise ValueError(_('invalid string'))
949 if sys.version_info[0] >= 3:
950 #UnicodeOption is same as StrOption in python 3+
951 class UnicodeOption(StrOption):
955 class UnicodeOption(Option):
956 "represents the choice of a unicode string"
959 'polymorphic_identity': 'unicode',
963 def _validate(self, value):
964 if not isinstance(value, unicode):
965 raise ValueError(_('invalid unicode'))
968 class SymLinkOption(BaseOption):
969 #__slots__ = ('_name', '_opt', '_state_opt', '_readonly', '_parent')
971 'polymorphic_identity': 'symlink',
973 #not return _opt consistencies
974 #_consistencies = None
976 def __init__(self, name, opt):
978 if not isinstance(opt, Option):
979 raise ValueError(_('malformed symlinkoption '
981 'for symlink {0}').format(name))
983 self._readonly = True
988 def __getattr__(self, name):
989 if name in ('_opt', '_opt_type', '_readonly', 'impl_getname'):
990 return object.__getattr__(self, name)
992 return getattr(self._opt, name)
994 def _impl_getstate(self, descr):
995 super(SymLinkOption, self)._impl_getstate(descr)
996 self._state_opt = descr.impl_get_path_by_opt(self._opt)
998 def _impl_setstate(self, descr):
999 self._opt = descr.impl_get_opt_by_path(self._state_opt)
1000 del(self._state_opt)
1001 super(SymLinkOption, self)._impl_setstate(descr)
1003 def impl_get_information(self, key, default=None):
1004 #FIXME ne devrait pas etre util si ?
1005 return self._opt.impl_get_information(key, default)
1008 class IPOption(Option):
1009 "represents the choice of an ip"
1010 #__slots__ = ('_private_only', '_allow_reserved')
1012 'polymorphic_identity': 'ip',
1015 def __init__(self, name, doc, default=None, default_multi=None,
1016 requires=None, multi=False, callback=None,
1017 callback_params=None, validator=None, validator_params=None,
1018 properties=None, private_only=False, allow_reserved=False,
1019 warnings_only=False):
1020 self._private_only = private_only
1021 self._allow_reserved = allow_reserved
1022 super(IPOption, self).__init__(name, doc, default=default,
1023 default_multi=default_multi,
1025 callback_params=callback_params,
1028 validator=validator,
1029 validator_params=validator_params,
1030 properties=properties,
1031 warnings_only=warnings_only)
1033 def _validate(self, value):
1034 # sometimes an ip term starts with a zero
1035 # but this does not fit in some case, for example bind does not like it
1036 for val in value.split('.'):
1037 if val.startswith("0") and len(val) > 1:
1038 raise ValueError(_('invalid IP'))
1039 # 'standard' validation
1041 IP('{0}/32'.format(value))
1043 raise ValueError(_('invalid IP'))
1045 def _second_level_validation(self, value):
1046 ip = IP('{0}/32'.format(value))
1047 if not self._allow_reserved and ip.iptype() == 'RESERVED':
1048 raise ValueError(_("invalid IP, mustn't not be in reserved class"))
1049 if self._private_only and not ip.iptype() == 'PRIVATE':
1050 raise ValueError(_("invalid IP, must be in private class"))
1053 class PortOption(Option):
1054 """represents the choice of a port
1055 The port numbers are divided into three ranges:
1056 the well-known ports,
1057 the registered ports,
1058 and the dynamic or private ports.
1059 You can actived this three range.
1060 Port number 0 is reserved and can't be used.
1061 see: http://en.wikipedia.org/wiki/Port_numbers
1063 #__slots__ = ('_allow_range', '_allow_zero', '_min_value', '_max_value')
1065 'polymorphic_identity': 'port',
1068 def __init__(self, name, doc, default=None, default_multi=None,
1069 requires=None, multi=False, callback=None,
1070 callback_params=None, validator=None, validator_params=None,
1071 properties=None, allow_range=False, allow_zero=False,
1072 allow_wellknown=True, allow_registred=True,
1073 allow_private=False, warnings_only=False):
1074 self._allow_range = allow_range
1075 self._min_value = None
1076 self._max_value = None
1077 ports_min = [0, 1, 1024, 49152]
1078 ports_max = [0, 1023, 49151, 65535]
1080 for index, allowed in enumerate([allow_zero,
1084 if self._min_value is None:
1086 self._min_value = ports_min[index]
1089 elif allowed and is_finally:
1090 raise ValueError(_('inconsistency in allowed range'))
1092 self._max_value = ports_max[index]
1094 if self._max_value is None:
1095 raise ValueError(_('max value is empty'))
1097 super(PortOption, self).__init__(name, doc, default=default,
1098 default_multi=default_multi,
1100 callback_params=callback_params,
1103 validator=validator,
1104 validator_params=validator_params,
1105 properties=properties,
1106 warnings_only=warnings_only)
1108 def _validate(self, value):
1109 if self._allow_range and ":" in str(value):
1110 value = str(value).split(':')
1112 raise ValueError('invalid part, range must have two values '
1114 if not value[0] < value[1]:
1115 raise ValueError('invalid port, first port in range must be'
1116 ' smaller than the second one')
1121 if not self._min_value <= int(val) <= self._max_value:
1122 raise ValueError('invalid port, must be an between {0} and {1}'
1123 ''.format(self._min_value, self._max_value))
1126 class NetworkOption(Option):
1127 "represents the choice of a network"
1128 #__slots__ = tuple()
1130 'polymorphic_identity': 'network',
1133 def _validate(self, value):
1137 raise ValueError(_('invalid network address'))
1139 def _second_level_validation(self, value):
1141 if ip.iptype() == 'RESERVED':
1142 raise ValueError(_("invalid network address, must not be in reserved class"))
1145 class NetmaskOption(Option):
1146 "represents the choice of a netmask"
1147 #__slots__ = tuple()
1149 'polymorphic_identity': 'netmask',
1152 def _validate(self, value):
1154 IP('0.0.0.0/{0}'.format(value))
1156 raise ValueError(_('invalid netmask address'))
1158 def _cons_network_netmask(self, opts, vals):
1159 #opts must be (netmask, network) options
1162 self.__cons_netmask(opts, vals[0], vals[1], False)
1164 def _cons_ip_netmask(self, opts, vals):
1165 #opts must be (netmask, ip) options
1168 self.__cons_netmask(opts, vals[0], vals[1], True)
1170 def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net):
1172 raise ConfigError(_('invalid len for opts'))
1175 ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
1177 #if cidr == 32, ip same has network
1178 if ip.prefixlen() != 32:
1180 IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
1181 make_net=not make_net)
1184 msg = _("invalid network {0} ({1}) "
1186 " this network is an IP")
1189 msg = _("invalid IP {0} ({1}) with netmask {2},"
1190 " this IP is a network")
1194 msg = _('invalid IP {0} ({1}) with netmask {2}')
1196 msg = _('invalid network {0} ({1}) with netmask {2}')
1198 raise ValueError(msg.format(val_ipnetwork, opts[1].impl_getname(),
1202 class BroadcastOption(Option):
1203 #__slots__ = tuple()
1205 'polymorphic_identity': 'broadcast',
1208 def _validate(self, value):
1210 IP('{0}/32'.format(value))
1212 raise ValueError(_('invalid broadcast address'))
1214 def _cons_broadcast(self, opts, vals):
1216 raise ConfigError(_('invalid len for vals'))
1219 broadcast, network, netmask = vals
1220 if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast):
1221 raise ValueError(_('invalid broadcast {0} ({1}) with network {2} '
1222 '({3}) and netmask {4} ({5})').format(
1223 broadcast, opts[0].impl_getname(), network,
1224 opts[1].impl_getname(), netmask, opts[2].impl_getname()))
1227 class DomainnameOption(Option):
1228 """represents the choice of a domain name
1229 netbios: for MS domain
1230 hostname: to identify the device
1232 fqdn: with tld, not supported yet
1234 #__slots__ = ('_dom_type', '_allow_ip', '_allow_without_dot', '_domain_re')
1236 'polymorphic_identity': 'domainname',
1239 def __init__(self, name, doc, default=None, default_multi=None,
1240 requires=None, multi=False, callback=None,
1241 callback_params=None, validator=None, validator_params=None,
1242 properties=None, allow_ip=False, type_='domainname',
1243 warnings_only=False, allow_without_dot=False):
1244 if type_ not in ['netbios', 'hostname', 'domainname']:
1245 raise ValueError(_('unknown type_ {0} for hostname').format(type_))
1246 self._dom_type = type_
1247 if allow_ip not in [True, False]:
1248 raise ValueError(_('allow_ip must be a boolean'))
1249 if allow_without_dot not in [True, False]:
1250 raise ValueError(_('allow_without_dot must be a boolean'))
1251 self._allow_ip = allow_ip
1252 self._allow_without_dot = allow_without_dot
1255 extrachar_mandatory = ''
1256 if self._dom_type == 'netbios':
1258 elif self._dom_type == 'hostname':
1260 elif self._dom_type == 'domainname':
1262 if allow_without_dot is False:
1263 extrachar_mandatory = '\.'
1267 self._domain_re = re.compile(r'^(?:[a-z][a-z\d\-{0}]{{,{1}}}{2}){3}$'
1268 ''.format(extrachar, length, extrachar_mandatory, end))
1269 super(DomainnameOption, self).__init__(name, doc, default=default,
1270 default_multi=default_multi,
1272 callback_params=callback_params,
1275 validator=validator,
1276 validator_params=validator_params,
1277 properties=properties,
1278 warnings_only=warnings_only)
1280 def _validate(self, value):
1281 if self._allow_ip is True:
1283 IP('{0}/32'.format(value))
1287 if self._dom_type == 'domainname' and not self._allow_without_dot and \
1289 raise ValueError(_("invalid domainname, must have dot"))
1290 if len(value) > 255:
1291 raise ValueError(_("invalid domainname's length (max 255)"))
1293 raise ValueError(_("invalid domainname's length (min 2)"))
1294 if not self._domain_re.search(value):
1295 raise ValueError(_('invalid domainname'))
1298 class EmailOption(DomainnameOption):
1299 #__slots__ = tuple()
1301 'polymorphic_identity': 'email',
1303 username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
1305 def _validate(self, value):
1306 splitted = value.split('@', 1)
1308 username, domain = splitted
1310 raise ValueError(_('invalid email address, should contains one @'
1312 if not self.username_re.search(username):
1313 raise ValueError(_('invalid username in email address'))
1314 super(EmailOption, self)._validate(domain)
1317 class URLOption(DomainnameOption):
1318 #__slots__ = tuple()
1320 'polymorphic_identity': 'url',
1322 proto_re = re.compile(r'(http|https)://')
1323 path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
1325 def _validate(self, value):
1326 match = self.proto_re.search(value)
1328 raise ValueError(_('invalid url, should start with http:// or '
1330 value = value[len(match.group(0)):]
1332 splitted = value.split('/', 1)
1334 domain, files = splitted
1339 splitted = domain.split(':', 1)
1341 domain, port = splitted
1344 domain = splitted[0]
1346 if not 0 <= int(port) <= 65535:
1347 raise ValueError(_('invalid url, port must be an between 0 and '
1349 # validate domainname
1350 super(URLOption, self)._validate(domain)
1352 if files is not None and files != '' and not self.path_re.search(files):
1353 raise ValueError(_('invalid url, should ends with filename'))
1356 class FilenameOption(Option):
1357 #__slots__ = tuple()
1359 'polymorphic_identity': 'file',
1361 path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
1363 def _validate(self, value):
1364 match = self.path_re.search(value)
1366 raise ValueError(_('invalid filename'))
1369 class OptionDescription(BaseOption):
1370 """Config's schema (organisation, group) and container of Options
1371 The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
1373 #_slots = ('_name', '_requires', '_cache_paths', '_group_type',
1374 # '_state_group_type', '_properties', '_children',
1375 # '_cache_consistencies', '_calc_properties', '__weakref__',
1376 # '_readonly', '_impl_informations', '_state_requires',
1377 # '_stated', '_state_readonly')
1379 'polymorphic_identity': 'optiondescription',
1382 def __init__(self, name, doc, children, requires=None, properties=None):
1384 :param children: a list of options (including optiondescriptions)
1387 super(OptionDescription, self).__init__(name, doc=doc, requires=requires, properties=properties)
1390 child_names = [child.impl_getname() for child in children]
1391 #better performance like this
1392 valid_child = copy(child_names)
1395 for child in valid_child:
1397 raise ConflictError(_('duplicate option name: '
1398 '{0}').format(child))
1400 for child in children:
1401 if child._parent is not None:
1402 raise ConflictError(_('duplicate option: '
1403 '{0}').format(child))
1404 self._children.append(child) # = (tuple(child_names), tuple(children))
1405 self._cache_paths = None
1406 self._cache_consistencies = None
1407 # the group_type is useful for filtering OptionDescriptions in a config
1408 self._optiondescription_group_type = groups.default
1410 def impl_getproperties(self):
1412 for prop in self._properties:
1415 def impl_getrequires(self):
1417 return self._requires
1419 def impl_getdoc(self):
1420 return self.impl_get_information('doc')
1422 def impl_validate(self, *args):
1426 def __getattr__(self, name):
1428 if name.startswith('_') or name.startswith('impl_'):
1429 return object.__getattribute__(self, name)
1431 #FIXME regression ... devrait etre un query !
1432 for child in self._children:
1433 if child.impl_getname() == name:
1436 #return session.query(child._type).filter_by(id=child.id).first()
1437 #return pouet#self._children[1][self._children[0].index(name)]
1440 raise AttributeError(_('unknown Option {0} '
1441 'in OptionDescription {1}'
1442 '').format(name, self.impl_getname()))
1444 #def impl_getkey(self, config):
1445 # return tuple([child.impl_getkey(getattr(config, child.impl_getname()))
1446 # for child in self.impl_getchildren()])
1448 def impl_getpaths(self, include_groups=False, _currpath=None):
1449 """returns a list of all paths in self, recursively
1450 _currpath should not be provided (helps with recursion)
1452 if _currpath is None:
1455 for option in self.impl_getchildren():
1456 attr = option.impl_getname()
1457 if isinstance(option, OptionDescription):
1459 paths.append('.'.join(_currpath + [attr]))
1460 paths += option.impl_getpaths(include_groups=include_groups,
1461 _currpath=_currpath + [attr])
1463 paths.append('.'.join(_currpath + [attr]))
1466 def impl_getchildren(self):
1467 #FIXME dans la base ??
1468 return self._children
1469 #for child in self._children:
1470 # yield(session.query(child._type).filter_by(id=child.id).first())
1472 def impl_build_cache(self,
1476 _consistencies=None,
1477 force_no_consistencies=False):
1478 if _currpath is None and self._cache_paths is not None:
1481 if _currpath is None:
1484 if not force_no_consistencies:
1488 if cache_path is None:
1491 for option in self.impl_getchildren():
1492 attr = option.impl_getname()
1493 if option.id is None:
1494 raise SystemError(_("an option's id should not be None "
1495 "for {0}").format(option.impl_getname()))
1496 if option.id in cache_option:
1497 raise ConflictError(_('duplicate option: {0}').format(option))
1498 cache_option.append(option.id)
1499 if not force_no_consistencies:
1500 option._readonly = True
1501 cache_path.append(str('.'.join(_currpath + [attr])))
1502 if not isinstance(option, OptionDescription):
1503 if not force_no_consistencies and \
1504 option._consistencies is not []:
1505 for consistency in option._consistencies:
1506 func = consistency.func
1507 all_cons_opts = consistency.options
1508 for opt in all_cons_opts:
1509 _consistencies.setdefault(opt,
1513 _currpath.append(attr)
1514 option.impl_build_cache(cache_path,
1518 force_no_consistencies)
1521 self._cache_paths = (tuple(cache_option), tuple(cache_path))
1522 if not force_no_consistencies:
1523 if _consistencies != {}:
1524 self._cache_consistencies = {}
1525 for opt, cons in _consistencies.items():
1526 if opt.id not in cache_option:
1527 raise ConfigError(_('consistency with option {0} which is not in Config').format(opt.impl_getname()))
1528 self._cache_consistencies[opt] = tuple(cons)
1529 self._readonly = True
1531 def impl_get_opt_by_path(self, path):
1534 idx = self._cache_paths[1].index(path)
1535 opt_id = self._cache_paths[0][idx]
1536 return session.query(BaseOption).filter_by(id=opt_id).first()
1538 raise AttributeError(_('no option for path {0}').format(path))
1540 def impl_get_opt_by_id(self, opt_id):
1543 idx = self._cache_paths[0].index(opt_id)
1544 return session.query(BaseOption).filter_by(id=opt_id).first()
1546 raise AttributeError(_('no id {0} found').format(opt_id))
1548 def impl_get_path_by_opt(self, opt):
1550 return self._cache_paths[1][self._cache_paths[0].index(opt.id)]
1552 raise AttributeError(_('no option {0} found').format(opt))
1554 # ____________________________________________________________
1555 def impl_set_group_type(self, group_type):
1556 """sets a given group object to an OptionDescription
1558 :param group_type: an instance of `GroupType` or `MasterGroupType`
1559 that lives in `setting.groups`
1561 if self._optiondescription_group_type != groups.default:
1562 raise TypeError(_('cannot change group_type if already set '
1563 '(old {0}, new {1})').format(self._optiondescription_group_type,
1565 if isinstance(group_type, groups.GroupType):
1566 self._optiondescription_group_type = group_type
1567 if isinstance(group_type, groups.MasterGroupType):
1568 #if master (same name has group) is set
1569 #for collect all slaves
1572 for child in self.impl_getchildren():
1573 if isinstance(child, OptionDescription):
1574 raise ValueError(_("master group {0} shall not have "
1575 "a subgroup").format(self.impl_getname()))
1576 if isinstance(child, SymLinkOption):
1577 raise ValueError(_("master group {0} shall not have "
1578 "a symlinkoption").format(self.impl_getname()))
1579 if not child.impl_is_multi():
1580 raise ValueError(_("not allowed option {0} "
1582 ": this option is not a multi"
1583 "").format(child.impl_getname(), self.impl_getname()))
1584 if child._name == self.impl_getname():
1585 child._multitype = multitypes.master
1588 slaves.append(child)
1590 raise ValueError(_('master group with wrong'
1591 ' master name for {0}'
1592 ).format(self.impl_getname()))
1593 #FIXME debut reecriture
1594 ##master_callback, master_callback_params = master.impl_get_callback()
1595 #if master._callback is not None and master._callback[1] is not None:
1596 # for key, callbacks in master._callback[1].items():
1597 # for callbk in callbacks:
1598 # if isinstance(callbk, tuple):
1599 # if callbk[0] in slaves:
1600 # raise ValueError(_("callback of master's option shall "
1601 # "not refered a slave's ones"))
1602 master._master_slaves = tuple(slaves)
1603 for child in self.impl_getchildren():
1605 child._master_slaves = master
1606 child._multitype = multitypes.slave
1608 raise ValueError(_('group_type: {0}'
1609 ' not allowed').format(group_type))
1611 def impl_get_group_type(self):
1612 return getattr(groups, self._optiondescription_group_type)
1614 def _valid_consistency(self, option, value, context, index):
1615 if self._cache_consistencies is None:
1617 #consistencies is something like [('_cons_not_equal', (opt1, opt2))]
1618 consistencies = self._cache_consistencies.get(option)
1619 if consistencies is not None:
1620 for func, all_cons_opts in consistencies:
1621 #all_cons_opts[0] is the option where func is set
1622 ret = all_cons_opts[0]._launch_consistency(func, option,
1630 # ____________________________________________________________
1633 def _impl_getstate(self, descr=None):
1634 """enables us to export into a dict
1635 :param descr: parent :class:`tiramisu.option.OptionDescription`
1638 self.impl_build_cache()
1640 super(OptionDescription, self)._impl_getstate(descr)
1641 self._state_group_type = str(self._optiondescription_group_type)
1642 for option in self.impl_getchildren():
1643 option._impl_getstate(descr)
1645 def __getstate__(self):
1646 """special method to enable the serialization with pickle
1650 # the `_state` attribute is a flag that which tells us if
1651 # the serialization can be performed
1653 except AttributeError:
1654 # if cannot delete, _impl_getstate never launch
1655 # launch it recursivement
1656 # _stated prevent __getstate__ launch more than one time
1657 # _stated is delete, if re-serialize, re-lauch _impl_getstate
1658 self._impl_getstate()
1660 return super(OptionDescription, self).__getstate__(stated)
1662 def _impl_setstate(self, descr=None):
1663 """enables us to import from a dict
1664 :param descr: parent :class:`tiramisu.option.OptionDescription`
1667 self._cache_paths = None
1668 self._cache_consistencies = None
1669 self.impl_build_cache(force_no_consistencies=True)
1671 self._optiondescription_group_type = getattr(groups, self._state_group_type)
1672 del(self._state_group_type)
1673 super(OptionDescription, self)._impl_setstate(descr)
1674 for option in self.impl_getchildren():
1675 option._impl_setstate(descr)
1677 def __setstate__(self, state):
1678 super(OptionDescription, self).__setstate__(state)
1681 except AttributeError:
1682 self._impl_setstate()
1685 def validate_requires_arg(requires, name):
1686 """check malformed requirements
1687 and tranform dict to internal tuple
1689 :param requires: have a look at the
1690 :meth:`tiramisu.setting.Settings.apply_requires` method to
1692 the description of the requires dictionary
1694 if requires is None:
1699 # start parsing all requires given by user (has dict)
1700 # transforme it to a tuple
1701 for require in requires:
1702 if not type(require) == dict:
1703 raise ValueError(_("malformed requirements type for option:"
1704 " {0}, must be a dict").format(name))
1705 valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
1707 unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
1708 if unknown_keys != frozenset():
1709 raise ValueError('malformed requirements for option: {0}'
1710 ' unknown keys {1}, must only '
1714 # prepare all attributes
1716 option = require['option']
1717 expected = require['expected']
1718 action = require['action']
1720 raise ValueError(_("malformed requirements for option: {0}"
1721 " require must have option, expected and"
1722 " action keys").format(name))
1723 inverse = require.get('inverse', False)
1724 if inverse not in [True, False]:
1725 raise ValueError(_('malformed requirements for option: {0}'
1726 ' inverse must be boolean'))
1727 transitive = require.get('transitive', True)
1728 if transitive not in [True, False]:
1729 raise ValueError(_('malformed requirements for option: {0}'
1730 ' transitive must be boolean'))
1731 same_action = require.get('same_action', True)
1732 if same_action not in [True, False]:
1733 raise ValueError(_('malformed requirements for option: {0}'
1734 ' same_action must be boolean'))
1736 if not isinstance(option, Option):
1737 raise ValueError(_('malformed requirements '
1738 'must be an option in option {0}').format(name))
1739 if option.impl_is_multi():
1740 raise ValueError(_('malformed requirements option {0} '
1741 'should not be a multi').format(name))
1742 if expected is not None:
1744 option._validate(expected)
1745 except ValueError as err:
1746 raise ValueError(_('malformed requirements second argument '
1747 'must be valid for option {0}'
1748 ': {1}').format(name, err))
1749 if action in config_action:
1750 if inverse != config_action[action]:
1751 raise ValueError(_("inconsistency in action types"
1753 " action: {1}").format(name, action))
1755 config_action[action] = inverse
1756 if action not in ret_requires:
1757 ret_requires[action] = {}
1758 if option not in ret_requires[action]:
1759 ret_requires[action][option] = (option, [expected], action,
1760 inverse, transitive, same_action)
1762 ret_requires[action][option][1].append(expected)
1763 ## transform dict to tuple
1765 #for opt_requires in ret_requires.values():
1767 # for require in opt_requires.values():
1768 # ret_action.append((require[0], tuple(require[1]), require[2],
1769 # require[3], require[4], require[5]))
1770 # ret.append(tuple(ret_action))
1774 def validate_callback(callback, callback_params, type_):
1775 if type(callback) != FunctionType:
1776 raise ValueError(_('{0} should be a function').format(type_))
1777 if callback_params is not None:
1778 if not isinstance(callback_params, dict):
1779 raise ValueError(_('{0}_params should be a dict').format(type_))
1780 for key, callbacks in callback_params.items():
1781 if key != '' and len(callbacks) != 1:
1782 raise ValueError(_('{0}_params with key {1} should not have '
1783 'length different to 1').format(type_,
1785 if not isinstance(callbacks, tuple):
1786 raise ValueError(_('{0}_params should be tuple for key "{1}"'
1787 ).format(type_, key))
1788 for callbk in callbacks:
1789 if isinstance(callbk, tuple):
1790 option, force_permissive = callbk
1791 if type_ == 'validator' and not force_permissive:
1792 raise ValueError(_('validator not support tuple'))
1793 if not isinstance(option, Option) and not \
1794 isinstance(option, SymLinkOption):
1795 raise ValueError(_('{0}_params should have an option '
1796 'not a {0} for first argument'
1797 ).format(type_, type(option)))
1798 if force_permissive not in [True, False]:
1799 raise ValueError(_('{0}_params should have a boolean'
1800 ' not a {0} for second argument'
1801 ).format(type_, type(
1805 Base.metadata.create_all(engine)
1806 Session = sessionmaker(bind=engine)