# -*- 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 RequirementError, PropertiesOptionError from tiramisu.i18n import _ default_encoding = 'utf-8' 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') default_properties = ('expire', 'validator') storage_type = 'dictionary' 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): "namespace for the master/slaves" class MultiType(str): pass class DefaultMultiType(MultiType): pass class MasterMultiType(MultiType): pass class SlaveMultiType(MultiType): pass multitypes = MultiTypeModule() def populate_multitypes(): "populates the master/slave namespace" setattr(multitypes, 'default', multitypes.DefaultMultiType('default')) setattr(multitypes, 'master', multitypes.MasterMultiType('master')) setattr(multitypes, 'slave', multitypes.SlaveMultiType('slave')) populate_multitypes() class Property(object): "a property is responsible of the option's value access rules" __slots__ = ('_setting', '_properties', '_opt', '_path') def __init__(self, setting, prop, opt=None, path=None): self._opt = opt self._path = path self._setting = setting self._properties = prop def append(self, propname): self._properties.add(propname) self._setting._setproperties(self._properties, self._opt, self._path) def remove(self, propname): if propname in self._properties: self._properties.remove(propname) self._setting._setproperties(self._properties, self._opt, self._path) def reset(self): self._setting.reset(path=self._path) def __contains__(self, propname): return propname in self._properties def __repr__(self): return str(list(self._properties)) #____________________________________________________________ class Settings(object): "``Config()``'s configuration options" __slots__ = ('context', '_owner', '_p_') def __init__(self, context, storage): """ initializer :param context: the root config :param storage: the storage type - dictionnary -> in memory - sqlite3 -> persistent """ # generic owner self._owner = owners.user self.context = context import_lib = 'tiramisu.storage.{0}.setting'.format(storage_type) self._p_ = __import__(import_lib, globals(), locals(), ['Settings'], -1).Settings(storage) #____________________________________________________________ # properties methods def __contains__(self, propname): "enables the pythonic 'in' syntaxic sugar" return propname in self._getproperties() def __repr__(self): return str(list(self._getproperties())) def __getitem__(self, opt): if opt is None: path = None else: path = self._get_opt_path(opt) return self._getitem(opt, path) def _getitem(self, opt, path): return Property(self, self._getproperties(opt, path), opt, path) def __setitem__(self, opt, value): raise ValueError('you must only append/remove properties') def reset(self, opt=None, all_properties=False): if all_properties and opt: raise ValueError(_('opt and all_properties must not be set ' 'together in reset')) if all_properties: self._p_.reset_all_propertives() else: if opt is None: path = None else: path = self._get_opt_path(opt) self._p_.reset_properties(path) self.context.cfgimpl_reset_cache() def _getproperties(self, opt=None, path=None, is_apply_req=True): if opt is None: props = self._p_.getproperties(path, default_properties) else: if path is None: raise ValueError(_('if opt is not None, path should not be None in _getproperties')) ntime = None if self._p_.hascache('property', path): ntime = time() is_cached, props = self._p_.getcache('property', path, ntime) if is_cached: return props if is_apply_req: self.apply_requires(opt, path) props = self._p_.getproperties(path, opt._properties) if 'expire' in self: if ntime is None: ntime = time() self._p_.setcache('property', path, props, ntime + expires_time) return props def append(self, propname): "puts property propname in the Config's properties attribute" Property(self, self._getproperties()).append(propname) def remove(self, propname): "deletes property propname in the Config's properties attribute" Property(self, self._getproperties()).remove(propname) def _setproperties(self, properties, opt, path): """save properties for specified opt (never save properties if same has option properties) """ if opt is None: self._p_.setproperties(path, properties) else: if set(opt._properties) == properties: self._p_.reset_properties(path) else: self._p_.setproperties(path, properties) self.context.cfgimpl_reset_cache() #____________________________________________________________ def validate_properties(self, opt_or_descr, is_descr, is_write, path, value=None, force_permissive=False, force_properties=None): """ validation upon the properties related to `opt_or_descr` :param opt_or_descr: an option or an option description object :param force_permissive: behaves as if the permissive property was present :param is_descr: we have to know if we are in an option description, just because the mandatory property doesn't exist there :param is_write: in the validation process, an option is to be modified, the behavior can be different (typically with the `frozen` property) """ # opt properties properties = copy(self._getproperties(opt_or_descr, path)) # remove opt permissive properties -= self._p_.getpermissive(path) # remove global permissive if need self_properties = copy(self._getproperties()) if force_permissive is True or 'permissive' in self_properties: properties -= self._p_.getpermissive() # 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()._isempty( 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') # at this point an option should not remain in properties if properties != frozenset(): props = list(properties) if 'frozen' in properties: raise PropertiesOptionError(_('cannot change the value for ' 'option {0} this option is' ' frozen').format( opt_or_descr._name), props) else: raise PropertiesOptionError(_("trying to access to an option " "named: {0} with properties {1}" "").format(opt_or_descr._name, str(props)), props) def setpermissive(self, permissive, path=None): if not isinstance(permissive, tuple): raise TypeError(_('permissive must be a tuple')) self._p_.setpermissive(path, 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 reset_cache(self, only_expired): if only_expired: self._p_.reset_expired_cache('property', time()) else: self._p_.reset_all_cache('property') def apply_requires(self, opt, path): "carries out the jit (just in time requirements between options" if opt._requires is None: return # filters the callbacks setting = Property(self, self._getproperties(opt, path, False), opt, path=path) descr = self.context.cfgimpl_get_description() for requires in opt._requires: matches = False for require in requires: option, expected, action, inverse, \ transitive, same_action = require reqpath = self._get_opt_path(option) if reqpath == path or reqpath.startswith(path + '.'): raise RequirementError(_("malformed requirements " "imbrication detected for option:" " '{0}' with requirement on: " "'{1}'").format(path, reqpath)) try: value = self.context._getattr(reqpath, force_permissive=True) except PropertiesOptionError, err: if not transitive: continue properties = err.proptype if same_action and action not in properties: raise RequirementError(_("option '{0}' has " "requirement's property " "error: " "{1} {2}").format(opt._name, reqpath, properties)) # transitive action, force expected value = expected[0] inverse = False except AttributeError: raise AttributeError(_("required option not found: " "{0}").format(reqpath)) if (not inverse and value in expected or inverse and value not in expected): matches = True setting.append(action) # the calculation cannot be carried out break # no requirement has been triggered, then just reverse the action if not matches: setting.remove(action) def _get_opt_path(self, opt): return self.context.cfgimpl_get_description().impl_get_path_by_opt(opt)