add docstring and some docs

This commit is contained in:
gwen
2012-10-05 16:00:07 +02:00
parent 8ca58c508d
commit d3dc40033b
11 changed files with 398 additions and 547 deletions

View File

@ -16,15 +16,15 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# The original `Config` design model is unproudly borrowed from
# The original `Config` design model is unproudly borrowed from
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence
# ____________________________________________________________
from copy import copy
from tiramisu.error import (PropertiesOptionError, ConfigError, NotFoundError,
AmbigousOptionError, ConflictConfigError, NoMatchingOptionFound,
from tiramisu.error import (PropertiesOptionError, ConfigError, NotFoundError,
AmbigousOptionError, ConflictConfigError, NoMatchingOptionFound,
MandatoryError, MethodCallError)
from tiramisu.option import (OptionDescription, Option, SymLinkOption,
from tiramisu.option import (OptionDescription, Option, SymLinkOption,
group_types, Multi, apply_requires)
from tiramisu.autolib import carry_out_calculation
@ -33,63 +33,59 @@ from tiramisu.autolib import carry_out_calculation
default_owner = 'user'
# ____________________________________________________________
class Config(object):
"properties attribute: the name of a property enables this property"
_cfgimpl_properties = ['hidden', 'disabled']
"mandatory means: a mandatory option has to have a value that is not None"
_cfgimpl_mandatory = True
_cfgimpl_frozen = True
_cfgimpl_owner = default_owner
_cfgimpl_toplevel = None
# TODO implement unicity by name
# _cfgimpl_unique_names = True
def __init__(self, descr, parent=None, **overrides):
""" Configuration option management master class
:param descr: describes the configuration schema
:type descr: an instance of ``option.OptionDescription``
:param overrides: can be used to set different default values
(see method ``override``)
:param parent: is None if the ``Config`` is root parent Config otherwise
:type parent: ``Config``
"""
self._cfgimpl_descr = descr
self._cfgimpl_value_owners = {}
self._cfgimpl_parent = parent
# `Config()` indeed takes care of the `Option()`'s values
"`Config()` indeed is in charge of the `Option()`'s values"
self._cfgimpl_values = {}
self._cfgimpl_previous_values = {}
# XXX warnings are a great idea, let's make up a better use of it
"warnings are a great idea, let's make up a better use of it"
self._cfgimpl_warnings = []
self._cfgimpl_toplevel = self._cfgimpl_get_toplevel()
# `freeze()` allows us to carry out this calculation again if necessary
self._cfgimpl_frozen = self._cfgimpl_toplevel._cfgimpl_frozen
'`freeze()` allows us to carry out this calculation again if necessary'
self._cfgimpl_frozen = self._cfgimpl_toplevel._cfgimpl_frozen
self._cfgimpl_build(overrides)
def _validate_duplicates(self, children):
"""duplicates Option names in the schema
:type children: list of `Option` or `OptionDescription`
"""
duplicates = []
for dup in children:
if dup._name not in duplicates:
duplicates.append(dup._name)
else:
raise ConflictConfigError('duplicate option name: '
raise ConflictConfigError('duplicate option name: '
'{0}'.format(dup._name))
# TODO implement unicity by name
# def _validate_duplicates_for_names(self, children):
# "validates duplicates names agains the whole config"
# rootconfig = self._cfgimpl_get_toplevel()
# if self._cfgimpl_unique_names:
# for dup in children:
# try:
# print dup._name
# try:
# print rootconfig.get(dup._name)
# except AttributeError:
# pass
# raise NotFoundError
# #rootconfig.get(dup._name)
# except NotFoundError:
# pass # no identical names, it's fine
# else:
# raise ConflictConfigError('duplicate option name: '
# '{0}'.format(dup._name))
def _cfgimpl_build(self, overrides):
"""
- builds the config object from the schema
- settles various default values for options
:param overrides: dict of options name:default values
"""
self._validate_duplicates(self._cfgimpl_descr._children)
for child in self._cfgimpl_descr._children:
if isinstance(child, Option):
if child.is_multi():
childdef = Multi(copy(child.getdefault()), config=self,
childdef = Multi(copy(child.getdefault()), config=self,
child=child)
self._cfgimpl_values[child._name] = childdef
self._cfgimpl_previous_values[child._name] = list(childdef)
@ -98,7 +94,7 @@ class Config(object):
else:
childdef = child.getdefault()
self._cfgimpl_values[child._name] = childdef
self._cfgimpl_previous_values[child._name] = childdef
self._cfgimpl_previous_values[child._name] = childdef
self._cfgimpl_value_owners[child._name] = 'default'
elif isinstance(child, OptionDescription):
self._validate_duplicates(child._children)
@ -106,10 +102,10 @@ class Config(object):
self.override(overrides)
def cfgimpl_update(self):
"dynamically adds `Option()` or `OptionDescription()`"
# Nothing is static. Everything evolve.
# FIXME this is an update for new options in the schema only
# see the update_child() method of the descr object
"""dynamically adds `Option()` or `OptionDescription()`
"""
# FIXME this is an update for new options in the schema only
# see the update_child() method of the descr object
for child in self._cfgimpl_descr._children:
if isinstance(child, Option):
if child._name not in self._cfgimpl_values:
@ -126,45 +122,50 @@ class Config(object):
self._cfgimpl_values[child._name] = Config(child, parent=self)
def override(self, overrides):
"""
overrides default values. This marks the overridden values as defaults.
:param overrides: is a dictionary of path strings to values.
"""
for name, value in overrides.iteritems():
homeconfig, name = self._cfgimpl_get_home_by_path(name)
homeconfig.setoption(name, value, 'default')
def cfgimpl_set_owner(self, owner):
":param owner: sets the default value for owner at the Config level"
self._cfgimpl_owner = owner
for child in self._cfgimpl_descr._children:
if isinstance(child, OptionDescription):
self._cfgimpl_values[child._name].cfgimpl_set_owner(owner)
# ____________________________________________________________
# properties methods
def _cfgimpl_has_properties(self):
"has properties means the Config's properties attribute is not empty"
return bool(len(self._cfgimpl_properties))
def _cfgimpl_has_property(self, propname):
"""has property propname in the Config's properties attribute
:param property: string wich is the name of the property"""
return propname in self._cfgimpl_properties
def cfgimpl_enable_property(self, propname):
"puts property propname in the Config's properties attribute"
if self._cfgimpl_parent != None:
raise MethodCallError("this method root_hide() shall not be"
"used with non-root Config() object")
"used with non-root Config() object")
if propname not in self._cfgimpl_properties:
self._cfgimpl_properties.append(propname)
def cfgimpl_disable_property(self, propname):
"deletes property propname in the Config's properties attribute"
if self._cfgimpl_parent != None:
raise MethodCallError("this method root_hide() shall not be"
"used with non-root Config() object")
"used with non-root Config() object")
if self._cfgimpl_has_property(propname):
self._cfgimpl_properties.remove(propname)
def cfgimpl_non_mandatory(self):
if self._cfgimpl_parent != None:
raise MethodCallError("this method root_mandatory machin() shall not be"
"used with non-root Confit() object")
rootconfig = self._cfgimpl_get_toplevel()
rootconfig._cfgimpl_mandatory = False
# ____________________________________________________________
# attribute methods
def __setattr__(self, name, value):
"attribute notation mechanism for the setting of the value of an option"
if name.startswith('_cfgimpl_'):
self.__dict__[name] = value
return
@ -174,9 +175,10 @@ class Config(object):
if type(getattr(self._cfgimpl_descr, name)) != SymLinkOption:
self._validate(name, getattr(self._cfgimpl_descr, name))
self.setoption(name, value, self._cfgimpl_owner)
def _validate(self, name, opt_or_descr):
apply_requires(opt_or_descr, self)
"validation for the setattr and the getattr"
apply_requires(opt_or_descr, self)
if not isinstance(opt_or_descr, Option) and \
not isinstance(opt_or_descr, OptionDescription):
raise TypeError('Unexpected object: {0}'.format(repr(opt_or_descr)))
@ -187,10 +189,11 @@ class Config(object):
if properties != []:
raise PropertiesOptionError("trying to access"
" to an option named: {0} with properties"
" {1}".format(name, str(properties)),
" {1}".format(name, str(properties)),
properties)
def _is_empty(self, opt):
"convenience method to know if an option is empty"
if (not opt.is_multi() and self._cfgimpl_values[opt._name] == None) or \
(opt.is_multi() and (self._cfgimpl_values[opt._name] == [] or \
None in self._cfgimpl_values[opt._name])):
@ -198,13 +201,14 @@ class Config(object):
return False
def __getattr__(self, name):
# attribute access by passing a path,
# for instance getattr(self, "creole.general.family.adresse_ip_eth0")
"attribute notation mechanism for accessing the value of an option"
# attribute access by passing a path,
# for instance getattr(self, "creole.general.family.adresse_ip_eth0")
if '.' in name:
homeconfig, name = self._cfgimpl_get_home_by_path(name)
return getattr(homeconfig, name)
opt_or_descr = getattr(self._cfgimpl_descr, name)
# symlink options
# symlink options
if type(opt_or_descr) == SymLinkOption:
return getattr(self, opt_or_descr.path)
if name not in self._cfgimpl_values:
@ -218,7 +222,7 @@ class Config(object):
raise AttributeError("%s object has no attribute %s" %
(self.__class__, name))
if not isinstance(opt_or_descr, OptionDescription):
# options with callbacks (fill or auto)
# options with callbacks (fill or auto)
if opt_or_descr.has_callback():
value = self._cfgimpl_values[name]
if (not opt_or_descr.is_frozen() or \
@ -228,11 +232,11 @@ class Config(object):
return value
else:
return value
result = carry_out_calculation(name,
result = carry_out_calculation(name,
callback=opt_or_descr.getcallback(),
callback_params=opt_or_descr.getcallback_params(),
config=self._cfgimpl_get_toplevel())
# this result **shall not** be a list
# this result **shall not** be a list
# for example, [1, 2, 3, None] -> [1, 2, 3, result]
if isinstance(result, list):
raise ConfigError('invalid calculated value returned'
@ -259,23 +263,20 @@ class Config(object):
if opt_or_descr.is_mandatory() and mandatory:
if self._is_empty(opt_or_descr) and \
opt_or_descr.is_empty_by_default():
raise MandatoryError("option: {0} is mandatory "
raise MandatoryError("option: {0} is mandatory "
"and shall have a value".format(name))
# frozen and force default
if opt_or_descr.is_forced_on_freeze():
return opt_or_descr.getdefault()
return self._cfgimpl_values[name]
# def __dir__(self):
# #from_type = dir(type(self))
# from_dict = list(self.__dict__)
# extras = list(self._cfgimpl_values)
# return sorted(set(extras + from_dict))
def unwrap_from_name(self, name):
# didn't have to stoop so low: `self.get()` must be the proper method
# **and it is slow**: it recursively searches into the namespaces
"""convenience method to extract and Option() object from the Config()
**and it is slow**: it recursively searches into the namespaces
:returns: Option()
"""
paths = self.getpaths(allpaths=True)
opts = dict([(path, self.unwrap_from_path(path)) for path in paths])
all_paths = [p.split(".") for p in self.getpaths()]
@ -283,17 +284,21 @@ class Config(object):
if name in pth:
return opts[".".join(pth)]
raise NotFoundError("name: {0} not found".format(name))
def unwrap_from_path(self, path):
# didn't have to stoop so low, `geattr(self, path)` is much better
# **fast**: finds the option directly in the appropriate namespace
"""convenience method to extract and Option() object from the Config()
and it is **fast**: finds the option directly in the appropriate
namespace
:returns: Option()
"""
if '.' in path:
homeconfig, path = self._cfgimpl_get_home_by_path(path)
return getattr(homeconfig._cfgimpl_descr, path)
return getattr(self._cfgimpl_descr, path)
def __delattr__(self, name):
# if you use delattr you are responsible for all bad things happening
"if you use delattr you are responsible for all bad things happening"
if name.startswith('_cfgimpl_'):
del self.__dict__[name]
return
@ -304,12 +309,17 @@ class Config(object):
self._cfgimpl_values[name] = getattr(opt, 'default', None)
def setoption(self, name, value, who=None):
#who is **not necessarily** a owner, because it cannot be a list
#FIXME : sortir le setoption pour les multi, ca ne devrait pas être la
"""effectively modifies the value of an Option()
(typically called by the __setattr__)
:param who: is an owner's name
who is **not necessarily** a owner, because it cannot be a list
:type who: string
"""
child = getattr(self._cfgimpl_descr, name)
if who == None:
if child.is_multi():
newowner = [self._cfgimpl_owner for i in range(len(value))]
newowner = [self._cfgimpl_owner for i in range(len(value))]
else:
newowner = self._cfgimpl_owner
else:
@ -323,7 +333,7 @@ class Config(object):
" {0} that is set to multi".format(name))
newowner = [who for i in range(len(value))]
else:
newowner = who
newowner = who
if type(child) != SymLinkOption:
if child.has_callback() and who=='default':
raise TypeError("trying to set a value to an option "
@ -336,13 +346,20 @@ class Config(object):
child.setowner(self, ['default' for i in range(len(child.getdefault()))])
self._cfgimpl_values[name] = Multi(copy(child.getdefault()),
config=self, child=child)
else:
else:
child.setowner(self, newowner)
else:
homeconfig = self._cfgimpl_get_toplevel()
child.setoption(homeconfig, value, who)
def set(self, **kwargs):
"""
"do what I mean"-interface to option setting. Searches all paths
starting from that config for matches of the optional arguments
and sets the found option if the match is not ambiguous.
:param kwargs: dict of name strings to values.
"""
all_paths = [p.split(".") for p in self.getpaths(allpaths=True)]
for key, value in kwargs.iteritems():
key_p = key.split('.')
@ -362,79 +379,87 @@ class Config(object):
'more than one option that ends with %s' % (key, ))
else:
raise NoMatchingOptionFound(
'there is no option that matches %s'
'there is no option that matches %s'
' or the option is hidden or disabled'% (key, ))
def get(self, name):
"""
much like the attribute access way, except that
the search for the option is performed recursively in the whole
configuration tree.
**carefull**: very slow !
"""
paths = self.getpaths(allpaths=True)
pathsvalues = []
for path in paths:
pathname = path.split('.')[-1]
if pathname == name:
try:
value = getattr(self, path)
return value
value = getattr(self, path)
return value
except Exception, e:
raise e
raise NotFoundError("option {0} not found in config".format(name))
raise NotFoundError("option {0} not found in config".format(name))
def _cfgimpl_get_home_by_path(self, path):
"""returns tuple (config, name)"""
""":returns: tuple (config, name)"""
path = path.split('.')
for step in path[:-1]:
self = getattr(self, step)
return self, path[-1]
def _cfgimpl_get_toplevel(self):
":returns: root config"
while self._cfgimpl_parent is not None:
self = self._cfgimpl_parent
return self
def _cfgimpl_get_path(self):
"the path in the attribute access meaning."
subpath = []
obj = self
while obj._cfgimpl_parent is not None:
subpath.insert(0, obj._cfgimpl_descr._name)
obj = obj._cfgimpl_parent
return ".".join(subpath)
# ______________________________________________________________________
def cfgimpl_previous_value(self, path):
"stores the previous value"
home, name = self._cfgimpl_get_home_by_path(path)
return home._cfgimpl_previous_values[name]
def get_previous_value(self, name):
"for the time being, only the previous Option's value is accessible"
return self._cfgimpl_previous_values[name]
# ______________________________________________________________________
def add_warning(self, warning):
"Config implements its own warning pile. Could be useful"
self._cfgimpl_get_toplevel()._cfgimpl_warnings.append(warning)
def get_warnings(self):
"Config implements its own warning pile"
return self._cfgimpl_get_toplevel()._cfgimpl_warnings
# ____________________________________________________________
# freeze and read-write statuses
# Config()'s status
def cfgimpl_freeze(self):
"cannot modify the frozen `Option`'s"
rootconfig = self._cfgimpl_get_toplevel()
rootconfig._cfgimpl_frozen = True
self._cfgimpl_frozen = True
def cfgimpl_unfreeze(self):
"can modify the Options that are frozen"
rootconfig = self._cfgimpl_get_toplevel()
rootconfig._cfgimpl_frozen = False
self._cfgimpl_frozen = False
def is_frozen(self):
# it should be the same value as self._cfgimpl_frozen...
"freeze flag at Config level"
rootconfig = self._cfgimpl_get_toplevel()
return rootconfig._cfgimpl_frozen
def is_mandatory(self):
rootconfig = self._cfgimpl_get_toplevel()
return rootconfig._cfgimpl_mandatory
def cfgimpl_read_only(self):
# convenience method to freeze, hidde and disable
"convenience method to freeze, hidde and disable"
self.cfgimpl_freeze()
rootconfig = self._cfgimpl_get_toplevel()
rootconfig.cfgimpl_disable_property('hidden')
@ -442,12 +467,35 @@ class Config(object):
rootconfig._cfgimpl_mandatory = True
def cfgimpl_read_write(self):
# convenience method to freeze, hidde and disable
"convenience method to freeze, hidde and disable"
self.cfgimpl_freeze()
rootconfig = self._cfgimpl_get_toplevel()
rootconfig.cfgimpl_enable_property('hidden')
rootconfig.cfgimpl_enable_property('disabled')
rootconfig._cfgimpl_mandatory = False
def cfgimpl_non_mandatory(self):
"""mandatory at the Config level means that the Config raises an error
if a mandatory option is found"""
if self._cfgimpl_parent != None:
raise MethodCallError("this method root_mandatory shall"
" not be used with non-root Confit() object")
rootconfig = self._cfgimpl_get_toplevel()
rootconfig._cfgimpl_mandatory = False
def cfgimpl_mandatory(self):
"""mandatory at the Config level means that the Config raises an error
if a mandatory option is found"""
if self._cfgimpl_parent != None:
raise MethodCallError("this method root_mandatory shall"
" not be used with non-root Confit() object")
rootconfig = self._cfgimpl_get_toplevel()
rootconfig._cfgimpl_mandatory = True
def is_mandatory(self):
"all mandatory Options shall have a value"
rootconfig = self._cfgimpl_get_toplevel()
return rootconfig._cfgimpl_mandatory
# ____________________________________________________________
def getkey(self):
return self._cfgimpl_descr.getkey(self)
@ -456,13 +504,15 @@ class Config(object):
return hash(self.getkey())
def __eq__(self, other):
"Config comparison"
return self.getkey() == other.getkey()
def __ne__(self, other):
"Config comparison"
return not self == other
# ______________________________________________________________________
def __iter__(self):
# iteration only on Options (not OptionDescriptions)
"iteration only on Options (not OptionDescriptions)"
for child in self._cfgimpl_descr._children:
if isinstance(child, Option):
try:
@ -481,12 +531,13 @@ class Config(object):
for child in self._cfgimpl_descr._children:
if isinstance(child, OptionDescription):
try:
if child.get_group_type() in groups:
if child.get_group_type() in groups:
yield child._name, getattr(self, child._name)
except:
pass # hidden, disabled option
# ______________________________________________________________________
def __str__(self, indent=""):
"Config's string representation"
lines = []
children = [(child._name, child)
for child in self._cfgimpl_descr._children]
@ -507,25 +558,27 @@ class Config(object):
return '\n'.join(lines)
def getpaths(self, include_groups=False, allpaths=False, mandatory=False):
"""returns a list of all paths in self, recursively, taking care of
"""returns a list of all paths in self, recursively, taking care of
the context of properties (hidden/disabled)
"""
paths = []
for path in self._cfgimpl_descr.getpaths(include_groups=include_groups):
try:
try:
value = getattr(self, path)
except MandatoryError:
if mandatory or allpaths:
paths.append(path)
except PropertiesOptionError:
if allpaths:
paths.append(path) # hidden or disabled or mandatory option added
paths.append(path) # option which have properties added
else:
paths.append(path)
return paths
return paths
def make_dict(config, flatten=False):
"""export the whole config into a `dict`
:returns: dict of Option's name (or path) and values"""
paths = config.getpaths()
pathsvalues = []
for path in paths:
@ -534,14 +587,19 @@ def make_dict(config, flatten=False):
else:
pathname = path
try:
value = getattr(config, path)
pathsvalues.append((pathname, value))
value = getattr(config, path)
pathsvalues.append((pathname, value))
except:
pass # this just a hidden or disabled option
options = dict(pathsvalues)
return options
def mandatory_warnings(config):
"""convenience function to trace Options that are mandatory and
where no value has been set
:returns: generator of mandatory Option's path
"""
mandatory = config._cfgimpl_get_toplevel()._cfgimpl_mandatory
config._cfgimpl_get_toplevel()._cfgimpl_mandatory = True
for path in config._cfgimpl_descr.getpaths(include_groups=True):

View File

@ -16,12 +16,12 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# The original `Config` design model is unproudly borrowed from
# The original `Config` design model is unproudly borrowed from
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence
# ____________________________________________________________
from tiramisu.basetype import HiddenBaseType, DisabledBaseType
from tiramisu.error import (ConfigError, ConflictConfigError, NotFoundError,
from tiramisu.error import (ConfigError, ConflictConfigError, NotFoundError,
RequiresError, RequirementRecursionError, MandatoryError)
requires_actions = [('hide', 'show'), ('enable', 'disable'), ('freeze', 'unfreeze')]
@ -31,25 +31,30 @@ for act1, act2 in requires_actions:
available_actions.extend([act1, act2])
reverse_actions[act1] = act2
reverse_actions[act2] = act1
# ____________________________________________________________
# OptionDescription authorized group_type values
"""
Three available group_types : `default`, `family`, `group` and
`master` (for master~slave group type). Notice that for a
master~slave group, the name of the group and the name of the
master option are identical.
"""
group_types = ['default', 'family', 'group', 'master']
# ____________________________________________________________
# multi types
# multi types
class Multi(list):
"container that support items for the values of list (multi) options"
"container that support items for the values of list (multi) options"
def __init__(self, lst, config, child):
self.config = config
self.child = child
super(Multi, self).__init__(lst)
def __setitem__(self, key, value):
return self.setoption(value, key)
def append(self, value):
self.setoption(value)
def setoption(self, value, key=None):
owners = self.child.getowner(self.config)
# None is replaced by default_multi
@ -77,7 +82,7 @@ class Multi(list):
self.config._cfgimpl_previous_values[self.child._name] = oldvalue
self.child.setowner(self.config, oldowner)
return ret
def pop(self, key):
oldowner = self.child.getowner(self.config)
oldowner.pop(key)
@ -86,11 +91,16 @@ class Multi(list):
# ____________________________________________________________
#
class Option(HiddenBaseType, DisabledBaseType):
#reminder: an Option object is **not** a container for the value
"""
Abstract base class for configuration option's
reminder: an Option object is **not** a container for the value
"""
"freeze means: cannot modify the value of an Option once set"
_frozen = False
"if an Option has been frozen, shall return the default value"
_force_default_on_freeze = False
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, mandatory=False, multi=False, callback=None,
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, mandatory=False, multi=False, callback=None,
callback_params=None):
self._name = name
self.doc = doc
@ -99,20 +109,20 @@ class Option(HiddenBaseType, DisabledBaseType):
self.multi = multi
if not self.multi and default_multi is not None:
raise ConfigError("a default_multi is set whereas multi is False"
" in option: {0}".format(name))
" in option: {0}".format(name))
if default_multi is not None and not self._validate(default_multi):
raise ConfigError("invalid default_multi value {0} "
"for option {1}".format(str(default_multi), name))
self.default_multi = default_multi
#if self.multi and default_multi is None:
# _cfgimpl_warnings[name] = DefaultMultiWarning
# _cfgimpl_warnings[name] = DefaultMultiWarning
if callback is not None and (default is not None or default_multi is not None):
raise ConfigError("defaut values not allowed if option: {0} "
raise ConfigError("defaut values not allowed if option: {0} "
"is calculated".format(name))
self.callback = callback
if self.callback is None and callback_params is not None:
raise ConfigError("params defined for a callback function but"
" no callback defined yet for option {0}".format(name))
" no callback defined yet for option {0}".format(name))
self.callback_params = callback_params
if self.multi == True:
if default == None:
@ -122,19 +132,19 @@ class Option(HiddenBaseType, DisabledBaseType):
"for option {1} : not list type".format(str(default), name))
else:
if default != None and not self.validate(default):
raise ConfigError("invalid default value {0} "
raise ConfigError("invalid default value {0} "
"for option {1}".format(str(default), name))
self.default = default
self.properties = [] # 'hidden', 'disabled'...
def validate(self, value):
if self.multi == False:
# None allows the reset of the value
if value != None:
return self._validate(value)
else:
if not isinstance(value, list):
raise ConfigError("invalid value {0} "
if not isinstance(value, list):
raise ConfigError("invalid value {0} "
"for option {1} which must be a list".format(value,
self._name))
for val in value:
@ -145,41 +155,53 @@ class Option(HiddenBaseType, DisabledBaseType):
return True
def getdefault(self):
"accessing the default value"
return self.default
def is_empty_by_default(self):
"no default value has been set yet"
if ((not self.is_multi() and self.default == None) or
(self.is_multi() and self.default == []) or None in self.default):
return True
return False
def force_default(self):
"if an Option has been frozen, shall return the default value"
self._force_default_on_freeze = True
def hascallback_and_isfrozen():
return self._frozen and self.has_callback()
def is_forced_on_freeze(self):
"if an Option has been frozen, shall return the default value"
return self._frozen and self._force_default_on_freeze
def getdoc(self):
"accesses the Option's doc"
return self.doc
def getcallback(self):
"a callback is only a link, the name of an external hook"
return self.callback
def has_callback(self):
"to know if a callback has been defined or not"
if self.callback == None:
return False
else:
return True
def getcallback_params(self):
"if a callback has been defined, returns his arity"
return self.callback_params
def setowner(self, config, owner):
# config *must* be only the **parent** config (not the toplevel config)
# owner is a **real* owner, a list is actually allowable here
"""
:param config: *must* be only the **parent** config
(not the toplevel config)
:param owner: is a **real* owner, that is a name or a list
which is allowable here
"""
name = self._name
if self.is_multi():
if not type(owner) == list:
@ -188,11 +210,14 @@ class Option(HiddenBaseType, DisabledBaseType):
config._cfgimpl_value_owners[name] = owner
def getowner(self, config):
# config *must* be only the **parent** config (not the toplevel config)
"config *must* be only the **parent** config (not the toplevel config)"
return config._cfgimpl_value_owners[self._name]
def setoption(self, config, value, who):
"who is **not necessarily** a owner because it cannot be a list"
"""changes the option's value with the value_owner's who
:param config: the parent config is necessary here to store the value
:param who : is **not necessarily** a owner because it cannot be a list
:type who: string """
name = self._name
if not self.validate(value):
raise ConfigError('invalid value %s for option %s' % (value, name))
@ -231,39 +256,37 @@ class Option(HiddenBaseType, DisabledBaseType):
# #option = getattr(config._cfgimpl_descr, reqname)
# # if not option.multi == True:
# # raise ConflictConfigError("an option with requires "
# # "has to be a list type : {0}".format(name))
# # "has to be a list type : {0}".format(name))
# if len(config._cfgimpl_values[reqname]) != len(value):
# raise ConflictConfigError("an option with requires "
# "has not the same length of the others "
# "in the group : {0}".format(reqname))
# "has not the same length of the others "
# "in the group : {0}".format(reqname))
if type(config._cfgimpl_values[name]) == Multi:
config._cfgimpl_previous_values[name] = list(config._cfgimpl_values[name])
else:
config._cfgimpl_previous_values[name] = config._cfgimpl_values[name]
config._cfgimpl_previous_values[name] = config._cfgimpl_values[name]
config._cfgimpl_values[name] = value
def getkey(self, value):
return value
# ____________________________________________________________
"freeze utility"
def freeze(self):
self._frozen = True
return True
def unfreeze(self):
self._frozen = False
def is_frozen(self):
return self._frozen
# ____________________________________________________________
def is_multi(self):
return self.multi
def is_mandatory(self):
return self._mandatory
class ChoiceOption(Option):
opt_type = 'string'
def __init__(self, name, doc, values, default=None,
requires=None, callback=None, callback_params=None,
multi=False, mandatory=False, open_values=False):
@ -273,7 +296,7 @@ class ChoiceOption(Option):
'{0}'.format(name))
self.open_values = open_values
super(ChoiceOption, self).__init__(name, doc, default=default,
callback=callback, callback_params=callback_params,
callback=callback, callback_params=callback_params,
requires=requires, multi=multi, mandatory=mandatory)
def _validate(self, value):
@ -284,11 +307,11 @@ class ChoiceOption(Option):
class BoolOption(Option):
opt_type = 'bool'
def _validate(self, value):
return isinstance(value, bool)
# config level validator
# config level validator
# def setoption(self, config, value, who):
# name = self._name
# if value and self._validator is not None:
@ -298,7 +321,7 @@ class BoolOption(Option):
class IntOption(Option):
opt_type = 'int'
def _validate(self, value):
return isinstance(value, int)
@ -310,36 +333,36 @@ class FloatOption(Option):
class StrOption(Option):
opt_type = 'string'
def _validate(self, value):
return isinstance(value, str)
class SymLinkOption(object):
opt_type = 'symlink'
def __init__(self, name, path):
self._name = name
self.path = path
self.path = path
def setoption(self, config, value, who):
setattr(config, self.path, value) # .setoption(self.path, value, who)
class IPOption(Option):
opt_type = 'ip'
def _validate(self, value):
# by now the validation is nothing but a string, use IPy instead
return isinstance(value, str)
class NetmaskOption(Option):
opt_type = 'netmask'
def _validate(self, value):
# by now the validation is nothing but a string, use IPy instead
return isinstance(value, str)
class ArbitraryOption(Option):
def __init__(self, name, doc, default=None, defaultfactory=None,
def __init__(self, name, doc, default=None, defaultfactory=None,
requires=None, multi=False, mandatory=False):
super(ArbitraryOption, self).__init__(name, doc, requires=requires,
multi=multi, mandatory=mandatory)
@ -356,16 +379,21 @@ class ArbitraryOption(Option):
return self.default
class OptionDescription(HiddenBaseType, DisabledBaseType):
"Config's schema (organisation) and container of Options"
"the group_type is an attribute useful for iteration on groups in a config"
group_type = 'default'
def __init__(self, name, doc, children, requires=None):
"""
:param children: is a list of option descriptions (including
``OptionDescription`` instances for nested namespaces).
"""
self._name = name
self.doc = doc
self._children = children
self._requires = requires
self._build()
self.properties = [] # 'hidden', 'disabled'...
def getdoc(self):
return self.doc
@ -383,12 +411,12 @@ class OptionDescription(HiddenBaseType, DisabledBaseType):
child._name))
self._children.append(child)
setattr(self, child._name, child)
def update_child(self, child):
"modification of an existing option"
# XXX : corresponds to the `redefine`, is it usefull
# XXX : corresponds to the `redefine`, is it usefull
pass
def getkey(self, config):
return tuple([child.getkey(getattr(config, child._name))
for child in self._children])
@ -415,40 +443,35 @@ class OptionDescription(HiddenBaseType, DisabledBaseType):
paths.append('.'.join(currpath + [attr]))
return paths
# ____________________________________________________________
def set_group_type(self, group_type):
":param group_type: string in group_types"
if group_type in group_types:
self.group_type = group_type
else:
raise ConfigError('not allowed value for group_type : {0}'.format(
group_type))
def get_group_type(self):
return self.group_type
# ____________________________________________________________
"actions API"
def hide(self):
super(OptionDescription, self).hide()
# FIXME : AND THE SUBCHILDREN ?
for child in self._children:
if isinstance(child, OptionDescription):
child.hide()
def show(self):
# FIXME : AND THE SUBCHILDREN ??
super(OptionDescription, self).show()
for child in self._children:
if isinstance(child, OptionDescription):
child.show()
# ____________________________________________________________
def disable(self):
super(OptionDescription, self).disable()
# FIXME : AND THE SUBCHILDREN ?
for child in self._children:
if isinstance(child, OptionDescription):
child.disable()
def enable(self):
# FIXME : AND THE SUBCHILDREN ?
super(OptionDescription, self).enable()
for child in self._children:
if isinstance(child, OptionDescription):
@ -456,7 +479,7 @@ class OptionDescription(HiddenBaseType, DisabledBaseType):
# ____________________________________________________________
def validate_requires_arg(requires, name):
# malformed requirements
"malformed requirements"
config_action = []
for req in requires:
if not type(req) == tuple and len(req) != 3:
@ -474,6 +497,7 @@ def validate_requires_arg(requires, name):
config_action.append(action)
def build_actions(requires):
"action are hide, show, enable, disable..."
trigger_actions = {}
for require in requires:
action = require[2]
@ -481,6 +505,7 @@ def build_actions(requires):
return trigger_actions
def apply_requires(opt, config):
"carries out the jit (just in time requirements between options"
if hasattr(opt, '_requires') and opt._requires is not None:
rootconfig = config._cfgimpl_get_toplevel()
validate_requires_arg(opt._requires, opt._name)
@ -508,4 +533,3 @@ def apply_requires(opt, config):
# no callback has been triggered, then just reverse the action
if not matches:
getattr(opt, reverse_actions[action])()