# -*- 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 . # # 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 import re from ..i18n import _ from ..setting import groups, undefined, owners # , log from .baseoption import BaseOption, SymLinkOption, Option, allowed_const_list from . import MasterSlaves from ..error import ConfigError, ConflictError from ..storage import get_storages_option from ..autolib import carry_out_calculation StorageOptionDescription = get_storages_option('optiondescription') name_regexp = re.compile(r'^[a-zA-Z\d\-_]*$') import sys if sys.version_info[0] >= 3: # pragma: optional cover xrange = range del(sys) class OptionDescription(BaseOption, StorageOptionDescription): """Config's schema (organisation, group) and container of Options The `OptionsDescription` objects lives in the `tiramisu.config.Config`. """ __slots__ = tuple() def __init__(self, name, doc, children, requires=None, properties=None): """ :param children: a list of options (including optiondescriptions) """ super(OptionDescription, self).__init__(name, doc=doc, requires=requires, properties=properties, callback=False) child_names = [] dynopt_names = [] for child in children: name = child.impl_getname() child_names.append(name) if isinstance(child, DynOptionDescription): dynopt_names.append(name) #better performance like this valid_child = copy(child_names) valid_child.sort() old = None for child in valid_child: if child == old: # pragma: optional cover raise ConflictError(_('duplicate option name: ' '{0}').format(child)) if dynopt_names: for dynopt in dynopt_names: if child != dynopt and child.startswith(dynopt): raise ConflictError(_('option must not start as ' 'dynoptiondescription')) old = child self._add_children(child_names, children) _setattr = object.__setattr__ _setattr(self, '_cache_consistencies', None) # the group_type is useful for filtering OptionDescriptions in a config _setattr(self, '_group_type', groups.default) def impl_getdoc(self): return self.impl_get_information('doc') def impl_validate(self, *args, **kwargs): """usefull for OptionDescription""" pass 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) """ return _impl_getpaths(self, include_groups, _currpath) def impl_build_cache(self, config, path='', _consistencies=None, cache_option=None, force_store_values=None): """validate duplicate option and set option has readonly option """ if cache_option is None: if self.impl_is_readonly(): raise ConfigError(_('option description seems to be part of an other ' 'config')) init = True _consistencies = {} cache_option = [] force_store_values = [] else: init = False for option in self._impl_getchildren(dyn=False): #FIXME specifique id for sqlalchemy? #FIXME avec sqlalchemy ca marche le multi parent ? (dans des configs différentes) cache_option.append(option._get_id()) if path == '': subpath = option.impl_getname() else: subpath = path + '.' + option.impl_getname() if isinstance(option, OptionDescription): option._set_readonly(False) option.impl_build_cache(config, subpath, _consistencies, cache_option, force_store_values) #cannot set multi option as OptionDescription requires else: option._set_readonly(True) is_multi = option.impl_is_multi() if not isinstance(option, SymLinkOption) and 'force_store_value' in option.impl_getproperties(): force_store_values.append((subpath, option)) for func, all_cons_opts, params in option._get_consistencies(): option._valid_consistencies(all_cons_opts[1:], init=False) if func not in allowed_const_list and is_multi: is_slave = option.impl_is_master_slaves() if not is_slave: raise ValueError(_('malformed consistency option "{0}" ' 'must be a master/slaves').format( option.impl_getname())) masterslaves = option.impl_get_master_slaves() for opt in all_cons_opts: if func not in allowed_const_list and is_multi: if not opt.impl_is_master_slaves(): raise ValueError(_('malformed consistency option "{0}" ' 'must not be a multi for "{1}"').format( option.impl_getname(), opt.impl_getname())) elif masterslaves != opt.impl_get_master_slaves(): raise ValueError(_('malformed consistency option "{0}" ' 'must be in same master/slaves for "{1}"').format( option.impl_getname(), opt.impl_getname())) _consistencies.setdefault(opt, []).append((func, all_cons_opts, params)) is_slave = None if is_multi: all_requires = option.impl_getrequires() if all_requires != tuple(): for requires in all_requires: for require in requires: #if option in require is a multi: # * option in require must be a master or a slave # * current option must be a slave (and only a slave) # * option in require and current option must be in same master/slaves require_opt = require[0] if require_opt.impl_is_multi(): if is_slave is None: is_slave = option.impl_is_master_slaves('slave') if is_slave: masterslaves = option.impl_get_master_slaves() if is_slave and require_opt.impl_is_master_slaves(): if masterslaves != require_opt.impl_get_master_slaves(): raise ValueError(_('malformed requirements option {0} ' 'must be in same master/slaves for {1}').format( require_opt.impl_getname(), option.impl_getname())) else: raise ValueError(_('malformed requirements option {0} ' 'must not be a multi for {1}').format( require_opt.impl_getname(), option.impl_getname())) if init: session = config._impl_values._p_.getsession() if len(cache_option) != len(set(cache_option)): for idx in xrange(1, len(cache_option) + 1): opt = cache_option.pop(0) if opt in cache_option: raise ConflictError(_('duplicate option: {0}').format(opt)) if _consistencies != {}: self._cache_consistencies = {} for opt, cons in _consistencies.items(): if opt._get_id() not in cache_option: # pragma: optional cover raise ConfigError(_('consistency with option {0} ' 'which is not in Config').format( opt.impl_getname())) self._cache_consistencies[opt] = tuple(cons) self._cache_force_store_values = force_store_values self._set_readonly(False) del(session) def impl_build_force_store_values(self, config): session = config._impl_values._p_.getsession() for subpath, option in self._cache_force_store_values: value = config.cfgimpl_get_values()._get_cached_value(option, path=subpath, validate=False, trusted_cached_properties=False, validate_properties=True) if option.impl_is_master_slaves('slave'): # problem with index raise ConfigError(_('a slave ({0}) cannot have ' 'force_store_value property').format(subpath)) if option._is_subdyn(): raise ConfigError(_('a dynoption ({0}) cannot have ' 'force_store_value property').format(subpath)) config._impl_values._p_.setvalue(subpath, value, owners.forced, None, session) # ____________________________________________________________ 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: # pragma: optional cover 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): children = self.impl_getchildren() for child in children: if isinstance(child, SymLinkOption): # pragma: optional cover raise ValueError(_("master group {0} shall not have " "a symlinkoption").format(self.impl_getname())) if not isinstance(child, Option): # pragma: optional cover raise ValueError(_("master group {0} shall not have " "a subgroup").format(self.impl_getname())) if not child.impl_is_multi(): # pragma: optional cover raise ValueError(_("not allowed option {0} " "in group {1}" ": this option is not a multi" "").format(child.impl_getname(), self.impl_getname())) #length of master change slaves length self._set_has_dependency() MasterSlaves(self.impl_getname(), children) else: # pragma: optional cover raise ValueError(_('group_type: {0}' ' not allowed').format(group_type)) 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_option() 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_consistencies = None self.impl_build_cache_option() descr = self self._group_type = getattr(groups, self._state_group_type) if isinstance(self._group_type, groups.MasterGroupType): MasterSlaves(self.impl_getname(), self.impl_getchildren(), validate=False) del(self._state_group_type) super(OptionDescription, self)._impl_setstate(descr) for option in self._impl_getchildren(dyn=False): option._impl_setstate(descr) def __setstate__(self, state): super(OptionDescription, self).__setstate__(state) try: self._stated except AttributeError: self._impl_setstate() def _impl_get_suffixes(self, context): callback, callback_params = self.impl_get_callback() values = carry_out_calculation(self, context=context, callback=callback, callback_params=callback_params) if isinstance(values, Exception): raise values if len(values) > len(set(values)): raise ConfigError(_('DynOptionDescription callback return not uniq value')) for val in values: if not isinstance(val, str) or re.match(name_regexp, val) is None: raise ValueError(_("invalid suffix: {0} for option").format(val)) return values def _impl_search_dynchild(self, name=undefined, context=undefined): ret = [] for child in self._impl_st_getchildren(context, only_dyn=True): cname = child.impl_getname() if name is undefined or name.startswith(cname): path = cname for value in child._impl_get_suffixes(context): if name is undefined: ret.append(SynDynOptionDescription(child, cname + value, path + value, value)) elif name == cname + value: return SynDynOptionDescription(child, name, path + value, value) return ret def _impl_get_dynchild(self, child, suffix): name = child.impl_getname() + suffix path = self.impl_getname() + suffix + '.' + name if isinstance(child, OptionDescription): return SynDynOptionDescription(child, name, path, suffix) else: return child._impl_to_dyn(name, path) def _impl_getchildren(self, dyn=True, context=undefined): for child in self._impl_st_getchildren(context): cname = child.impl_getname() if dyn and child.impl_is_dynoptiondescription(): path = cname for value in child._impl_get_suffixes(context): yield SynDynOptionDescription(child, cname + value, path + value, value) else: yield child def impl_getchildren(self): return list(self._impl_getchildren()) def __getattr__(self, name, context=undefined): if name.startswith('_'): # or name.startswith('impl_'): return object.__getattribute__(self, name) if '.' in name: path = name.split('.')[0] subpath = '.'.join(name.split('.')[1:]) return self.__getattr__(path, context=context).__getattr__(subpath, context=context) return self._getattr(name, context=context) class DynOptionDescription(OptionDescription): def __init__(self, name, doc, children, requires=None, properties=None, callback=None, callback_params=None): super(DynOptionDescription, self).__init__(name, doc, children, requires, properties) for child in children: if isinstance(child, OptionDescription): if child.impl_get_group_type() != groups.master: raise ConfigError(_('cannot set optiondescription in a ' 'dynoptiondescription')) for chld in child._impl_getchildren(): chld._impl_setsubdyn(self) if isinstance(child, SymLinkOption): raise ConfigError(_('cannot set symlinkoption in a ' 'dynoptiondescription')) if isinstance(child, SymLinkOption): raise ConfigError(_('cannot set symlinkoption in a ' 'dynoptiondescription')) child._impl_setsubdyn(self) self.impl_set_callback(callback, callback_params) def _validate_callback(self, callback, callback_params): if callback is None: raise ConfigError(_('callback is mandatory for dynoptiondescription')) class SynDynOptionDescription(object): __slots__ = ('_opt', '_name', '_path', '_suffix') def __init__(self, opt, name, path, suffix): self._opt = opt self._name = name self._path = path self._suffix = suffix def __getattr__(self, name, context=undefined): if name in dir(self._opt): return getattr(self._opt, name) return self._opt._getattr(name, suffix=self._suffix, context=context) def impl_getname(self): return self._name def _impl_getchildren(self, dyn=True, context=undefined): children = [] for child in self._opt._impl_getchildren(): children.append(self._opt._impl_get_dynchild(child, self._suffix)) return children def impl_getchildren(self): return self._impl_getchildren() def impl_getpath(self, context): return self._path def impl_getpaths(self, include_groups=False, _currpath=None): return _impl_getpaths(self, include_groups, _currpath) def _impl_getopt(self): return self._opt def _impl_getpaths(klass, include_groups, _currpath): """returns a list of all paths in klass, recursively _currpath should not be provided (helps with recursion) """ if _currpath is None: _currpath = [] paths = [] for option in klass._impl_getchildren(): attr = option.impl_getname() if option.impl_is_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