# -*- coding: utf-8 -*- "master slaves support" # Copyright (C) 2014-2018 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 # ____________________________________________________________ import weakref from itertools import chain from ..i18n import _ from ..setting import groups, undefined, OptionBag from .optiondescription import OptionDescription from .syndynoptiondescription import SynDynMasterSlaves from .option import Option from ..error import SlaveError, PropertiesOptionError, RequirementError from ..function import ParamOption class MasterSlaves(OptionDescription): __slots__ = ('master', 'slaves') def __init__(self, name, doc, children, requires=None, properties=None): super(MasterSlaves, self).__init__(name, doc, children, requires=requires, properties=properties) self._group_type = groups.master slaves = [] if len(children) < 2: raise ValueError(_('a master and a slave are mandatories in masterslaves "{}"').format(name)) master = children[0] for idx, child in enumerate(children): if child.impl_is_symlinkoption(): raise ValueError(_('masterslaves "{0}" shall not have ' "a symlinkoption").format(self.impl_get_display_name())) if not isinstance(child, Option): raise ValueError(_('masterslaves "{0}" shall not have ' 'a subgroup').format(self.impl_get_display_name())) if not child.impl_is_multi(): raise ValueError(_('only multi option allowed in masterslaves "{0}" but option ' '"{1}" is not a multi').format(self.impl_get_display_name(), child.impl_get_display_name())) if idx != 0 and child.impl_getdefault() != []: raise ValueError(_('not allowed default value for option "{0}" ' 'in masterslaves "{1}"' '').format(child.impl_get_display_name(), self.impl_get_display_name())) # no empty property for save if idx != 0: child_properties = list(child._properties) child_properties.remove('empty') child._properties = frozenset(child_properties) slaves.append(child) child._add_dependency(self) child._master_slaves = weakref.ref(self) callback, callback_params = master.impl_get_callback() if callback is not None and callback_params != None: for callbk in chain(callback_params.args, callback_params.kwargs.values()): if isinstance(callbk, ParamOption): if callbk.option in slaves: raise ValueError(_("callback of master's option shall " "not refered a slave's ones")) # master should not have requires, only MasterSlaves should have # so move requires to MasterSlaves # if MasterSlaves has requires to, cannot manage the move so raises master_requires = getattr(master, '_requires', None) if master_requires: if self.impl_getrequires(): raise RequirementError(_('master {} have requirement, but MasterSlaves {} too' '').format(master.impl_getname(), self.impl_getname())) master_calproperties = getattr(master, '_calc_properties', None) if master_calproperties: if properties is not None: self.validate_properties(name, master_calproperties, frozenset(properties)) setattr(self, '_calc_properties', master_calproperties) setattr(self, '_requires', master_requires) delattr(master, '_requires') all_requires = getattr(self, '_requires', None) if all_requires: for requires_ in all_requires: for require in requires_: for require_opt, values in require[0]: if require_opt.impl_is_multi(): if require_opt.impl_is_master_slaves(): raise ValueError(_('malformed requirements option "{0}" ' 'must not be in slave for "{1}"').format( require_opt.impl_getname(), self.impl_getname())) def is_master(self, opt): master = self._children[0][0] return opt.impl_getname() == master or (opt.impl_is_dynsymlinkoption() and opt.opt.impl_getname() == master) def getmaster(self): return self._children[1][0] def getslaves(self): for slave in self._children[1][1:]: yield slave def in_same_group(self, opt): if opt.impl_is_dynsymlinkoption(): c_opt = opt.opt else: c_opt = opt return c_opt in self._children[1] def reset(self, values, option_bag, _commit=True): config_bag = option_bag.config_bag.copy() config_bag.remove_validation() for slave in self.getslaves(): slave_path = slave.impl_getpath() soption_bag = OptionBag() soption_bag.set_option(slave, slave_path, None, config_bag) values.reset(soption_bag, _commit=_commit) def pop(self, values, index, option_bag, slaves=undefined): if slaves is undefined: slaves = self.getslaves() config_bag = option_bag.config_bag.copy() config_bag.remove_validation() for slave in slaves: slave_path = slave.impl_getpath() slavelen = values._p_.get_max_length(slave_path) soption_bag = OptionBag() soption_bag.set_option(slave, slave_path, index, config_bag) # do not check force_default_on_freeze soption_bag.properties = set() if not values.is_default_owner(soption_bag, validate_meta=False): if slavelen > index: values._p_.resetvalue_index(slave_path, index, True) if slavelen > index + 1: for idx in range(index + 1, slavelen): if values._p_.hasvalue(slave_path, idx): values._p_.reduce_index(slave_path, idx) def reset_cache(self, path, values, settings, resetted_opts): master = self.getmaster() slaves = self.getslaves() self._reset_cache(path, master, slaves, values, settings, resetted_opts) def _reset_cache(self, path, master, slaves, values, settings, resetted_opts): super().reset_cache(path, values, settings, resetted_opts) mpath = master.impl_getpath() master.reset_cache(mpath, values, settings, None) for slave in slaves: spath = slave.impl_getpath() slave.reset_cache(spath, values, settings, None) resetted_opts.append(spath) def impl_validate_value(self, option, value, context): if isinstance(value, list) and len(value) < context._impl_length: raise SlaveError(_('cannot reduce length of the master "{}"' '').format(option.impl_get_display_name())) def impl_is_master_slaves(self, *args, **kwargs): return True def to_dynoption(self, rootpath: str, suffix: str) -> SynDynMasterSlaves: return SynDynMasterSlaves(self, rootpath, suffix)