# -*- coding: utf-8 -*- "sets the options of the configuration objects Config object itself" # Copyright (C) 2012-2013 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 General Public License as published by # the Free Software Foundation; either version 2 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # 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 rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ from time import time from copy import copy from tiramisu.error import RequirementRecursionError, PropertiesOptionError from tiramisu.i18n import _ expires_time = 5 ro_remove = ('permissive', 'hidden') ro_append = ('frozen', 'disabled', 'validator', 'everything_frozen', 'mandatory') rw_remove = ('permissive', 'everything_frozen', 'mandatory') rw_append = ('frozen', 'disabled', 'validator', 'hidden') class _const: """convenient class that emulates a module and builds constants (that is, unique names)""" class ConstError(TypeError): pass def __setattr__(self, name, value): if name in self.__dict__: raise self.ConstError, _("Can't rebind group ({})").format(name) self.__dict__[name] = value def __delattr__(self, name): if name in self.__dict__: raise self.ConstError, _("Can't unbind group ({})").format(name) raise ValueError(name) # ____________________________________________________________ class GroupModule(_const): "emulates a module to manage unique group (OptionDescription) names" class GroupType(str): """allowed normal group (OptionDescription) names *normal* means : groups that are not master """ pass class DefaultGroupType(GroupType): """groups that are default (typically 'default')""" pass class MasterGroupType(GroupType): """allowed normal group (OptionDescription) names *master* means : groups that have the 'master' attribute set """ pass # setting.groups (emulates a module) groups = GroupModule() def populate_groups(): "populates the available groups in the appropriate namespaces" groups.master = groups.MasterGroupType('master') groups.default = groups.DefaultGroupType('default') groups.family = groups.GroupType('family') # names are in the module now populate_groups() # ____________________________________________________________ class OwnerModule(_const): """emulates a module to manage unique owner names. owners are living in `Config._cfgimpl_value_owners` """ class Owner(str): """allowed owner names """ pass class DefaultOwner(Owner): """groups that are default (typically 'default')""" pass # setting.owners (emulates a module) owners = OwnerModule() def populate_owners(): """populates the available owners in the appropriate namespaces - 'user' is the generic is the generic owner. - 'default' is the config owner after init time """ setattr(owners, 'default', owners.DefaultOwner('default')) setattr(owners, 'user', owners.Owner('user')) def add_owner(name): """ :param name: the name of the new owner """ setattr(owners, name, owners.Owner(name)) setattr(owners, 'add_owner', add_owner) # names are in the module now populate_owners() class MultiTypeModule(_const): class MultiType(str): pass class DefaultMultiType(MultiType): pass class MasterMultiType(MultiType): pass class SlaveMultiType(MultiType): pass multitypes = MultiTypeModule() def populate_multitypes(): setattr(multitypes, 'default', multitypes.DefaultMultiType('default')) setattr(multitypes, 'master', multitypes.MasterMultiType('master')) setattr(multitypes, 'slave', multitypes.SlaveMultiType('slave')) populate_multitypes() class Property(object): __slots__ = ('_setting', '_properties', '_opt') def __init__(self, setting, prop, opt=None): self._opt = opt self._setting = setting self._properties = prop def append(self, propname): self._properties.add(propname) self._setting._set_properties(self._properties, self._opt) self._setting.context.cfgimpl_reset_cache() def remove(self, propname): if propname in self._properties: self._properties.remove(propname) self._setting._set_properties(self._properties, self._opt) self._setting.context.cfgimpl_reset_cache() def __contains__(self, propname): return propname in self._properties def __repr__(self): return str(list(self._properties)) #____________________________________________________________ class Setting(object): "``Config()``'s configuration options" __slots__ = ('context', '_properties', '_permissives', '_owner', '_cache') def __init__(self, context): # properties attribute: the name of a property enables this property # key is None for global properties self._properties = {None: set(('expire',))} # permissive properties self._permissives = {} # generic owner self._owner = owners.user self.context = context self._cache = {} #____________________________________________________________ # properties methods def __contains__(self, propname): return propname in self._get_properties() def __repr__(self): return str(list(self._get_properties())) def __getitem__(self, opt): return Property(self, self._get_properties(opt), opt) def __setitem__(self, opt, value): raise ValueError('you must only append/remove properties') def _get_properties(self, opt=None, is_apply_req=True): if opt is None: props = self._properties.get(opt, set()) else: exp = None if opt in self._cache: exp = time() props, created = self._cache[opt] if exp < created: return props if is_apply_req: apply_requires(opt, self.context) props = self._properties.get(opt, opt._properties) self._set_cache(opt, props, exp) return props def append(self, propname): "puts property propname in the Config's properties attribute" Property(self, self._get_properties()).append(propname) def remove(self, propname): "deletes property propname in the Config's properties attribute" Property(self, self._get_properties()).remove(propname) def _set_properties(self, properties, opt=None): """save properties for specified opt (never save properties if same has option properties) """ if opt is None: self._properties[opt] = properties else: if opt._properties == properties: if opt in self._properties: del(self._properties[opt]) else: self._properties[opt] = properties #____________________________________________________________ def validate_properties(self, opt_or_descr, is_descr, is_write, value=None, force_permissive=False, force_properties=None): #opt properties properties = copy(self._get_properties(opt_or_descr)) #remove opt permissive properties -= self._get_permissive(opt_or_descr) #remove global permissive if need self_properties = copy(self._get_properties()) if force_permissive is True or 'permissive' in self_properties: properties -= self._get_permissive() #global properties if force_properties is not None: self_properties.update(force_properties) #calc properties properties &= self_properties #mandatory and frozen are special properties if is_descr: properties -= frozenset(('mandatory', 'frozen')) else: if 'mandatory' in properties and \ not self.context.cfgimpl_get_values()._is_empty(opt_or_descr, value): properties.remove('mandatory') if is_write and 'everything_frozen' in self_properties: properties.add('frozen') elif 'frozen' in properties and not is_write: properties.remove('frozen') if properties != frozenset(): if 'frozen' in properties: raise_text = 'cannot change the value for option {0} this option is frozen' else: raise_text = "trying to access to an option named: {0} with properties {1}" raise PropertiesOptionError(_(raise_text).format(opt_or_descr._name, str(properties)), list(properties)) def _get_permissive(self, opt=None): return self._permissives.get(opt, frozenset()) def set_permissive(self, permissive, opt=None): if not isinstance(permissive, tuple): raise TypeError(_('permissive must be a tuple')) self._permissives[opt] = frozenset(permissive) #____________________________________________________________ def setowner(self, owner): ":param owner: sets the default value for owner at the Config level" if not isinstance(owner, owners.Owner): raise TypeError(_("invalid generic owner {0}").format(str(owner))) self._owner = owner def getowner(self): return self._owner #____________________________________________________________ def _read(self, remove, append): for prop in remove: self.remove(prop) for prop in append: self.append(prop) def read_only(self): "convenience method to freeze, hidde and disable" self._read(ro_remove, ro_append) def read_write(self): "convenience method to freeze, hidde and disable" self._read(rw_remove, rw_append) def _set_cache(self, opt, props, exp): if 'expire' in self: if exp is None: exp = time() self._cache[opt] = (props, time() + expires_time) def reset_cache(self, only_expired): if only_expired: exp = time() keys = self._cache.keys() for key in keys: props, created = self._cache[key] if exp > created: del(self._cache[key]) else: self._cache.clear() def apply_requires(opt, config): "carries out the jit (just in time requirements between options" def build_actions(requires): "action are hide, show, enable, disable..." trigger_actions = {} for require in requires: action = require[2] trigger_actions.setdefault(action, []).append(require) return trigger_actions #for symlink if hasattr(opt, '_requires') and opt._requires is not None: # filters the callbacks settings = config.cfgimpl_get_settings() setting = Property(settings, settings._get_properties(opt, False), opt) trigger_actions = build_actions(opt._requires) descr = config.cfgimpl_get_context().cfgimpl_get_description() optpath = descr.impl_get_path_by_opt(opt) for requires in trigger_actions.values(): matches = False for require in requires: if len(require) == 3: option, expected, action = require inverse = False elif len(require) == 4: option, expected, action, inverse = require path = descr.impl_get_path_by_opt(option) if path == optpath or path.startswith(optpath + '.'): raise RequirementRecursionError(_("malformed requirements " "imbrication detected for option: '{0}' " "with requirement on: '{1}'").format(optpath, path)) try: value = config.cfgimpl_get_context()._getattr(path, force_permissive=True) except PropertiesOptionError, err: properties = err.proptype raise PropertiesOptionError(_("option '{0}' has requirement's property error: " "{1} {2}").format(opt._name, path, properties), properties) except AttributeError: raise AttributeError(_("required option not found: " "{0}").format(path)) if value == expected: if inverse: setting.remove(action) else: setting.append(action) matches = True # no requirement has been triggered, then just reverse the action if not matches: if inverse: setting.append(action) else: setting.remove(action)