tiramisu/tiramisu/option/optiondescription.py

264 lines
11 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
# Copyright (C) 2014 Team tiramisu (see AUTHORS for all contributors)
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# 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.i18n import _
from tiramisu.setting import groups, log
from .baseoption import BaseOption
from . import MasterSlaves
from tiramisu.error import ConfigError, ConflictError, ValueWarning
class OptionDescription(BaseOption):
"""Config's schema (organisation, group) and container of Options
The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
"""
__slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
'_state_group_type', '_properties', '_children',
'_cache_consistencies', '_calc_properties', '__weakref__',
'_readonly', '_impl_informations', '_state_requires',
'_stated', '_state_readonly')
_opt_type = 'optiondescription'
def __init__(self, name, doc, children, requires=None, properties=None):
"""
:param children: a list of options (including optiondescriptions)
"""
super(OptionDescription, self).__init__(name, doc, requires, properties)
child_names = [child._name for child in children]
#better performance like this
valid_child = copy(child_names)
valid_child.sort()
old = None
for child in valid_child:
if child == old:
raise ConflictError(_('duplicate option name: '
'{0}').format(child))
old = child
self._children = (tuple(child_names), tuple(children))
self._cache_paths = None
self._cache_consistencies = None
# the group_type is useful for filtering OptionDescriptions in a config
self._group_type = groups.default
def impl_getdoc(self):
return self.impl_get_information('doc')
def __getattr__(self, name):
if name in self.__slots__:
return object.__getattribute__(self, name)
try:
return self._children[1][self._children[0].index(name)]
except ValueError:
log.debug('__getattr__', exc_info=True)
raise AttributeError(_('unknown Option {0} '
'in OptionDescription {1}'
'').format(name, self._name))
def impl_getkey(self, config):
return tuple([child.impl_getkey(getattr(config, child._name))
for child in self.impl_getchildren()])
def impl_getpaths(self, include_groups=False, _currpath=None):
"""returns a list of all paths in self, recursively
_currpath should not be provided (helps with recursion)
"""
if _currpath is None:
_currpath = []
paths = []
for option in self.impl_getchildren():
attr = option._name
if isinstance(option, OptionDescription):
if include_groups:
paths.append('.'.join(_currpath + [attr]))
paths += option.impl_getpaths(include_groups=include_groups,
_currpath=_currpath + [attr])
else:
paths.append('.'.join(_currpath + [attr]))
return paths
def impl_getchildren(self):
return self._children[1]
def impl_build_cache(self,
cache_path=None,
cache_option=None,
_currpath=None,
_consistencies=None,
force_no_consistencies=False):
if _currpath is None and self._cache_paths is not None:
# cache already set
return
if _currpath is None:
save = True
_currpath = []
if not force_no_consistencies:
_consistencies = {}
else:
save = False
if cache_path is None:
cache_path = []
cache_option = []
for option in self.impl_getchildren():
attr = option._name
if option in cache_option:
raise ConflictError(_('duplicate option: {0}').format(option))
cache_option.append(option)
if not force_no_consistencies:
option._readonly = True
cache_path.append(str('.'.join(_currpath + [attr])))
if not isinstance(option, OptionDescription):
if not force_no_consistencies and \
option._consistencies is not None:
for consistency in option._consistencies:
func, all_cons_opts, params = consistency
for opt in all_cons_opts:
_consistencies.setdefault(opt,
[]).append((func,
all_cons_opts,
params))
else:
_currpath.append(attr)
option.impl_build_cache(cache_path,
cache_option,
_currpath,
_consistencies,
force_no_consistencies)
_currpath.pop()
if save:
self._cache_paths = (tuple(cache_option), tuple(cache_path))
if not force_no_consistencies:
if _consistencies != {}:
self._cache_consistencies = {}
for opt, cons in _consistencies.items():
if opt not in cache_option:
raise ConfigError(_('consistency with option {0} which is not in Config').format(opt._name))
self._cache_consistencies[opt] = tuple(cons)
self._readonly = True
def impl_get_opt_by_path(self, path):
try:
return self._cache_paths[0][self._cache_paths[1].index(path)]
except ValueError:
raise AttributeError(_('no option for path {0}').format(path))
def impl_get_path_by_opt(self, opt):
try:
return self._cache_paths[1][self._cache_paths[0].index(opt)]
except ValueError:
raise AttributeError(_('no option {0} found').format(opt))
# ____________________________________________________________
def impl_set_group_type(self, group_type):
"""sets a given group object to an OptionDescription
:param group_type: an instance of `GroupType` or `MasterGroupType`
that lives in `setting.groups`
"""
if self._group_type != groups.default:
raise TypeError(_('cannot change group_type if already set '
'(old {0}, new {1})').format(self._group_type,
group_type))
if isinstance(group_type, groups.GroupType):
self._group_type = group_type
if isinstance(group_type, groups.MasterGroupType):
MasterSlaves(self._name, self.impl_getchildren())
else:
raise ValueError(_('group_type: {0}'
' not allowed').format(group_type))
def impl_get_group_type(self):
return self._group_type
def _valid_consistency(self, option, value, context, index):
if self._cache_consistencies is None:
return True
#consistencies is something like [('_cons_not_equal', (opt1, opt2))]
consistencies = self._cache_consistencies.get(option)
if consistencies is not None:
for func, all_cons_opts, params in consistencies:
warnings_only = params.get('warnings_only', False)
#all_cons_opts[0] is the option where func is set
try:
all_cons_opts[0]._launch_consistency(func, option,
value,
context, index,
all_cons_opts,
warnings_only)
except ValueError as err:
if warnings_only:
raise ValueWarning(err.message, option)
else:
raise err
def _impl_getstate(self, descr=None):
"""enables us to export into a dict
:param descr: parent :class:`tiramisu.option.OptionDescription`
"""
if descr is None:
self.impl_build_cache()
descr = self
super(OptionDescription, self)._impl_getstate(descr)
self._state_group_type = str(self._group_type)
for option in self.impl_getchildren():
option._impl_getstate(descr)
def __getstate__(self):
"""special method to enable the serialization with pickle
"""
stated = True
try:
# the `_state` attribute is a flag that which tells us if
# the serialization can be performed
self._stated
except AttributeError:
# if cannot delete, _impl_getstate never launch
# launch it recursivement
# _stated prevent __getstate__ launch more than one time
# _stated is delete, if re-serialize, re-lauch _impl_getstate
self._impl_getstate()
stated = False
return super(OptionDescription, self).__getstate__(stated)
def _impl_setstate(self, descr=None):
"""enables us to import from a dict
:param descr: parent :class:`tiramisu.option.OptionDescription`
"""
if descr is None:
self._cache_paths = None
self._cache_consistencies = None
self.impl_build_cache(force_no_consistencies=True)
descr = self
self._group_type = getattr(groups, self._state_group_type)
del(self._state_group_type)
super(OptionDescription, self)._impl_setstate(descr)
for option in self.impl_getchildren():
option._impl_setstate(descr)
def __setstate__(self, state):
super(OptionDescription, self).__setstate__(state)
try:
self._stated
except AttributeError:
self._impl_setstate()