1 # -*- coding: utf-8 -*-
2 "option types and option description for the configuration management"
3 # Copyright (C) 2012 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 # ____________________________________________________________
23 from tiramisu.autolib import special_owners
24 from tiramisu.basetype import HiddenBaseType, DisabledBaseType, ModeBaseType, modes
25 from tiramisu.error import (ConfigError, ConflictConfigError, NotFoundError,
26 SpecialOwnersError, RequiresError)
27 available_actions = ['hide', 'show', 'enable', 'disable']
28 reverse_actions = {'hide': 'show', 'show': 'hide',
29 'disable':'enable', 'enable': 'disable'}
30 # ____________________________________________________________
31 # OptionDescription authorized group_type values
32 group_types = ['default', 'family', 'group', 'master']
36 "container that support items for the values of list (multi) options"
37 def __init__(self, lst, config, child):
40 super(Multi, self).__init__(lst)
42 def __setitem__(self, key, value):
43 return self.setoption(value, key)
45 def append(self, value):
48 def setoption(self, value, key=None):
49 owners = self.child.getowner(self.config)
50 # None is replaced by default_multi
52 defval = self.child.getdefault()
53 if key is not None and len(defval) > key:
56 value = self.child.default_multi
59 who = self.config._cfgimpl_owner
60 if not self.child._validate(value):
61 raise ConfigError("invalid value {0} "
62 "for option {1}".format(str(value), self.child._name))
64 oldowner = self.child.getowner(self.config)
66 ret = super(Multi, self).append(value)
70 ret = super(Multi, self).__setitem__(key, value)
72 self.config._cfgimpl_previous_values[self.child._name] = oldvalue
73 self.child.setowner(self.config, oldowner)
77 oldowner = self.child.getowner(self.config)
79 self.child.setowner(self.config, oldowner)
80 super(Multi, self).pop(key)
81 # ____________________________________________________________
83 class Option(HiddenBaseType, DisabledBaseType, ModeBaseType):
84 #reminder: an Option object is **not** a container for the value
86 def __init__(self, name, doc, default=None, default_multi=None,
87 requires=None, mandatory=False, multi=False, callback=None,
88 callback_params=None, mode='normal'):
91 self._requires = requires
92 self._mandatory = mandatory
94 if not self.multi and default_multi is not None:
95 raise ConfigError("a default_multi is set whereas multi is False"
96 " in option: {0}".format(name))
97 if default_multi is not None and not self._validate(default_multi):
98 raise ConfigError("invalid default_multi value {0} "
99 "for option {1}".format(str(default_multi), name))
100 self.default_multi = default_multi
101 #if self.multi and default_multi is None:
102 # _cfgimpl_warnings[name] = DefaultMultiWarning
103 if callback is not None and (default is not None or default_multi is not None):
104 raise ConfigError("defaut values not allowed if option: {0}"
105 "is calculated".format(name))
106 self.callback = callback
107 if self.callback is None and callback_params is not None:
108 raise ConfigError("params defined for a callback function but"
109 " no callback defined yet for option {0}".format(name))
110 self.callback_params = callback_params
111 if mode not in modes:
112 raise ConfigError("mode {0} not available".format(mode))
114 if self.multi == True:
117 if not isinstance(default, list) or not self.validate(default):
118 raise ConfigError("invalid default value {0} "
119 "for option {1} : not list type".format(str(default), name))
121 if default != None and not self.validate(default):
122 raise ConfigError("invalid default value {0} "
123 "for option {1}".format(str(default), name))
124 self.default = default
126 def validate(self, value):
127 if self.multi == False:
128 # None allows the reset of the value
130 return self._validate(value)
132 if not isinstance(value, list):
133 raise ConfigError("invalid value {0} "
134 "for option {1} which must be a list".format(value,
138 # None allows the reset of the value
139 if not self._validate(val):
143 def getdefault(self):
149 def getcallback(self):
152 def getcallback_params(self):
153 return self.callback_params
155 def setowner(self, config, owner):
156 # config *must* be only the **parent** config (not the toplevel config)
157 # owner is a **real* owner, a list is actually allowable here
160 raise TypeError("trying to change a frozen option's owner: %s" % name)
161 if owner in special_owners:
162 if self.callback == None:
163 raise SpecialOwnersError("no callback specified for"
164 "option {0}".format(name))
166 if not type(owner) == list:
167 raise ConfigError("invalid owner for multi "
168 "option: {0}".format(self._name))
169 config._cfgimpl_value_owners[name] = owner
171 def getowner(self, config):
172 # config *must* be only the **parent** config (not the toplevel config)
173 return config._cfgimpl_value_owners[self._name]
175 def setoption(self, config, value, who):
176 "who is **not necessarily** a owner because it cannot be a list"
179 raise TypeError('trying to change a frozen option object: %s' % name)
180 # we want the possibility to reset everything
181 if who == "default" and value is None:
184 if not self.validate(value):
185 raise ConfigError('invalid value %s for option %s' % (value, name))
187 # changes the default value (and therefore resets the previous value)
188 if self._validate(value):
191 raise ConfigError("invalid value %s for option %s" % (value, name))
192 apply_requires(self, config)
193 # FIXME put the validation for the multi somewhere else
194 # # it is a multi **and** it has requires
195 # if self.multi == True:
196 # if type(value) != list:
197 # raise TypeError("value {0} must be a list".format(value))
198 # if self._requires is not None:
199 # for reqname in self._requires:
200 # # FIXME : verify that the slaves are all multi
201 # #option = getattr(config._cfgimpl_descr, reqname)
202 # # if not option.multi == True:
203 # # raise ConflictConfigError("an option with requires "
204 # # "has to be a list type : {0}".format(name))
205 # if len(config._cfgimpl_values[reqname]) != len(value):
206 # raise ConflictConfigError("an option with requires "
207 # "has not the same length of the others "
208 # "in the group : {0}".format(reqname))
209 if type(config._cfgimpl_values[name]) == Multi:
210 config._cfgimpl_previous_values[name] = list(config._cfgimpl_values[name])
212 config._cfgimpl_previous_values[name] = config._cfgimpl_values[name]
213 config._cfgimpl_values[name] = value
215 def getkey(self, value):
224 # ____________________________________________________________
228 def is_mandatory(self):
229 return self._mandatory
231 class ChoiceOption(Option):
234 def __init__(self, name, doc, values, default=None,
235 requires=None, callback=None, callback_params=None,
236 multi=False, mandatory=False, open_values=False):
238 if open_values not in [True, False]:
239 raise ConfigError('Open_values must be a boolean for '
241 self.open_values = open_values
242 super(ChoiceOption, self).__init__(name, doc, default=default,
243 callback=callback, callback_params=callback_params,
244 requires=requires, multi=multi, mandatory=mandatory)
246 def setoption(self, config, value, who):
248 super(ChoiceOption, self).setoption(config, value, who)
250 def _validate(self, value):
251 if not self.open_values:
252 return value is None or value in self.values
256 class BoolOption(Option):
259 # def __init__(self, name, doc, default=None, requires=None,
260 # validator=None, multi=False, mandatory=False):
261 # super(BoolOption, self).__init__(name, doc, default=default,
262 # requires=requires, multi=multi, mandatory=mandatory)
263 #self._validator = validator
265 def _validate(self, value):
266 return isinstance(value, bool)
268 # FIXME config level validator
269 # def setoption(self, config, value, who):
271 # if value and self._validator is not None:
272 # toplevel = config._cfgimpl_get_toplevel()
273 # self._validator(toplevel)
274 # super(BoolOption, self).setoption(config, value, who)
276 class IntOption(Option):
279 def _validate(self, value):
286 def setoption(self, config, value, who):
288 super(IntOption, self).setoption(config, value, who)
290 raise ConfigError(*e.args)
292 class FloatOption(Option):
295 def _validate(self, value):
302 def setoption(self, config, value, who):
304 super(FloatOption, self).setoption(config, float(value), who)
306 raise ConfigError(*e.args)
308 class StrOption(Option):
311 def _validate(self, value):
312 return isinstance(value, str)
314 def setoption(self, config, value, who):
316 super(StrOption, self).setoption(config, value, who)
318 raise ConfigError(*e.args)
320 class SymLinkOption(object): #(HiddenBaseType, DisabledBaseType):
323 def __init__(self, name, path):
327 def setoption(self, config, value, who):
329 setattr(config, self.path, value) # .setoption(self.path, value, who)
331 raise ConfigError(*e.args)
333 class IPOption(Option):
336 def _validate(self, value):
337 # by now the validation is nothing but a string, use IPy instead
338 return isinstance(value, str)
340 def setoption(self, config, value, who):
342 super(IPOption, self).setoption(config, value, who)
344 raise ConfigError(*e.args)
346 class NetmaskOption(Option):
349 def _validate(self, value):
350 # by now the validation is nothing but a string, use IPy instead
351 return isinstance(value, str)
353 def setoption(self, config, value, who):
355 super(NetmaskOption, self).setoption(config, value, who)
357 raise ConfigError(*e.args)
359 class ArbitraryOption(Option):
360 def __init__(self, name, doc, default=None, defaultfactory=None,
361 requires=None, multi=False, mandatory=False):
362 super(ArbitraryOption, self).__init__(name, doc, requires=requires,
363 multi=multi, mandatory=mandatory)
364 self.defaultfactory = defaultfactory
365 if defaultfactory is not None:
366 assert default is None
368 def _validate(self, value):
371 def getdefault(self):
372 if self.defaultfactory is not None:
373 return self.defaultfactory()
376 class OptionDescription(HiddenBaseType, DisabledBaseType, ModeBaseType):
377 group_type = 'default'
379 def __init__(self, name, doc, children, requires=None):
382 self._children = children
383 self._requires = requires
390 for child in self._children:
391 setattr(self, child._name, child)
393 def add_child(self, child):
394 "dynamically adds a configuration option"
395 #Nothing is static. Even the Mona Lisa is falling apart.
396 for ch in self._children:
397 if isinstance(ch, Option):
398 if child._name == ch._name:
399 raise ConflictConfigError("existing option : {0}".format(
401 self._children.append(child)
402 setattr(self, child._name, child)
404 def update_child(self, child):
405 "modification of an existing option"
406 # XXX : corresponds to the `redefine`, is it usefull
409 def getkey(self, config):
410 return tuple([child.getkey(getattr(config, child._name))
411 for child in self._children])
413 def getpaths(self, include_groups=False, currpath=None):
414 """returns a list of all paths in self, recursively
415 currpath should not be provided (helps with recursion)
420 for option in self._children:
422 if attr.startswith('_cfgimpl'):
424 value = getattr(self, attr)
425 if isinstance(value, OptionDescription):
427 paths.append('.'.join(currpath + [attr]))
428 currpath.append(attr)
429 paths += value.getpaths(include_groups=include_groups,
433 paths.append('.'.join(currpath + [attr]))
435 # ____________________________________________________________
437 def set_group_type(self, group_type):
438 if group_type in group_types:
439 self.group_type = group_type
441 raise ConfigError('not allowed value for group_type : {0}'.format(
444 def get_group_type(self):
445 return self.group_type
446 # ____________________________________________________________
448 super(OptionDescription, self).hide()
449 # FIXME : AND THE SUBCHILDREN ?
450 for child in self._children:
451 if isinstance(child, OptionDescription):
455 # FIXME : AND THE SUBCHILDREN ??
456 super(OptionDescription, self).show()
457 for child in self._children:
458 if isinstance(child, OptionDescription):
460 # ____________________________________________________________
462 super(OptionDescription, self).disable()
463 # FIXME : AND THE SUBCHILDREN ?
464 for child in self._children:
465 if isinstance(child, OptionDescription):
469 # FIXME : AND THE SUBCHILDREN ?
470 super(OptionDescription, self).enable()
471 for child in self._children:
472 if isinstance(child, OptionDescription):
474 # ____________________________________________________________
475 def apply_requires(opt, config):
476 if hasattr(opt, '_requires'):
477 if opt._requires is not None:
478 # malformed requirements
479 rootconfig = config._cfgimpl_get_toplevel()
480 for req in opt._requires:
481 if not type(req) == tuple and len(req) in (3, 4):
482 raise RequiresError("malformed requirements for option:"
483 " {0}".format(opt._name))
484 # all actions **must** be identical
485 actions = [req[2] for req in opt._requires]
489 raise RequiresError("malformed requirements for option:"
490 " {0}".format(opt._name))
491 # filters the callbacks
493 for req in opt._requires:
495 name, expected, action = req
498 name, expected, action, inverted = req
499 if inverted == 'inverted':
501 homeconfig, shortname = \
502 rootconfig._cfgimpl_get_home_by_path(name)
503 # FIXME: doesn't work with 'auto' or 'fill' yet
504 # (copy the code from the __getattr__
505 if shortname in homeconfig._cfgimpl_values:
506 value = homeconfig._cfgimpl_values[shortname]
507 if (not inverted and value == expected) or \
508 (inverted and value != expected):
509 if action not in available_actions:
510 raise RequiresError("malformed requirements"
511 " for option: {0}".format(opt._name))
512 getattr(opt, action)() #.hide() or show() or...
514 else: # option doesn't exist ! should not happen...
515 raise NotFoundError("required option not found: "
517 # no callback has been triggered, then just reverse the action
519 getattr(opt, reverse_actions[action])()