1 # -*- coding: utf-8 -*-
2 "sets the options of the configuration objects Config object itself"
3 # Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors)
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 # The original `Config` design model is unproudly borrowed from
20 # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
21 # the whole pypy projet is under MIT licence
22 # ____________________________________________________________
25 from tiramisu.error import RequirementError, PropertiesOptionError
26 from tiramisu.i18n import _
28 default_encoding = 'utf-8'
30 ro_remove = ('permissive', 'hidden')
31 ro_append = ('frozen', 'disabled', 'validator', 'everything_frozen', 'mandatory')
32 rw_remove = ('permissive', 'everything_frozen', 'mandatory')
33 rw_append = ('frozen', 'disabled', 'validator', 'hidden')
34 default_properties = ('expire', 'validator')
38 """convenient class that emulates a module
39 and builds constants (that is, unique names)"""
40 class ConstError(TypeError):
43 def __setattr__(self, name, value):
44 if name in self.__dict__:
45 raise self.ConstError, _("can't rebind group ({})").format(name)
46 self.__dict__[name] = value
48 def __delattr__(self, name):
49 if name in self.__dict__:
50 raise self.ConstError, _("can't unbind group ({})").format(name)
51 raise ValueError(name)
54 # ____________________________________________________________
55 class GroupModule(_const):
56 "emulates a module to manage unique group (OptionDescription) names"
58 """allowed normal group (OptionDescription) names
59 *normal* means : groups that are not master
63 class DefaultGroupType(GroupType):
64 """groups that are default (typically 'default')"""
67 class MasterGroupType(GroupType):
68 """allowed normal group (OptionDescription) names
69 *master* means : groups that have the 'master' attribute set
72 # setting.groups (emulates a module)
73 groups = GroupModule()
76 def populate_groups():
77 "populates the available groups in the appropriate namespaces"
78 groups.master = groups.MasterGroupType('master')
79 groups.default = groups.DefaultGroupType('default')
80 groups.family = groups.GroupType('family')
82 # names are in the module now
86 # ____________________________________________________________
87 class OwnerModule(_const):
88 """emulates a module to manage unique owner names.
90 owners are living in `Config._cfgimpl_value_owners`
93 """allowed owner names
97 class DefaultOwner(Owner):
98 """groups that are default (typically 'default')"""
100 # setting.owners (emulates a module)
101 owners = OwnerModule()
104 def populate_owners():
105 """populates the available owners in the appropriate namespaces
107 - 'user' is the generic is the generic owner.
108 - 'default' is the config owner after init time
110 setattr(owners, 'default', owners.DefaultOwner('default'))
111 setattr(owners, 'user', owners.Owner('user'))
115 :param name: the name of the new owner
117 setattr(owners, name, owners.Owner(name))
118 setattr(owners, 'add_owner', add_owner)
120 # names are in the module now
124 class MultiTypeModule(_const):
125 "namespace for the master/slaves"
126 class MultiType(str):
129 class DefaultMultiType(MultiType):
132 class MasterMultiType(MultiType):
135 class SlaveMultiType(MultiType):
138 multitypes = MultiTypeModule()
141 def populate_multitypes():
142 "populates the master/slave namespace"
143 setattr(multitypes, 'default', multitypes.DefaultMultiType('default'))
144 setattr(multitypes, 'master', multitypes.MasterMultiType('master'))
145 setattr(multitypes, 'slave', multitypes.SlaveMultiType('slave'))
147 populate_multitypes()
150 class Property(object):
151 "a property is responsible of the option's value access rules"
152 __slots__ = ('_setting', '_properties', '_opt')
154 def __init__(self, setting, prop, opt=None):
156 self._setting = setting
157 self._properties = prop
159 def append(self, propname):
160 self._properties.add(propname)
161 self._setting._set_properties(self._properties, self._opt)
163 def remove(self, propname):
164 if propname in self._properties:
165 self._properties.remove(propname)
166 self._setting._set_properties(self._properties, self._opt)
169 self._setting.reset(opt=self._opt)
171 def __contains__(self, propname):
172 return propname in self._properties
175 return str(list(self._properties))
178 #____________________________________________________________
179 class Setting(object):
180 "``Config()``'s configuration options"
181 __slots__ = ('context', '_properties', '_permissives', '_owner', '_cache')
183 def __init__(self, context):
184 # properties attribute: the name of a property enables this property
185 # key is None for global properties
186 self._properties = {}
187 # permissive properties
188 self._permissives = {}
190 self._owner = owners.user
191 self.context = context
194 #____________________________________________________________
196 def __contains__(self, propname):
197 return propname in self._get_properties()
200 return str(list(self._get_properties()))
202 def __getitem__(self, opt):
203 return Property(self, self._get_properties(opt), opt)
205 def __setitem__(self, opt, value):
206 raise ValueError('you must only append/remove properties')
208 def reset(self, opt=None, all_properties=False):
209 if all_properties and opt:
210 raise ValueError(_('opt and all_properties must not be set '
211 'together in reset'))
213 self._properties = {}
216 del(self._properties[opt])
219 self.context.cfgimpl_reset_cache()
221 def _get_properties(self, opt=None, is_apply_req=True):
223 props = self._properties.get(opt, set(default_properties))
226 if opt in self._cache:
228 props, created = self._cache[opt]
232 self.apply_requires(opt)
233 props = self._properties.get(opt, set(opt._properties))
234 self._set_cache(opt, props, exp)
237 def append(self, propname):
238 "puts property propname in the Config's properties attribute"
239 Property(self, self._get_properties()).append(propname)
241 def remove(self, propname):
242 "deletes property propname in the Config's properties attribute"
243 Property(self, self._get_properties()).remove(propname)
245 def _set_properties(self, properties, opt=None):
246 """save properties for specified opt
247 (never save properties if same has option properties)
250 self._properties[opt] = properties
252 if set(opt._properties) == properties:
253 if opt in self._properties:
254 del(self._properties[opt])
256 self._properties[opt] = properties
257 self.context.cfgimpl_reset_cache()
259 #____________________________________________________________
260 def validate_properties(self, opt_or_descr, is_descr, is_write,
261 value=None, force_permissive=False,
262 force_properties=None):
264 properties = copy(self._get_properties(opt_or_descr))
265 #remove opt permissive
266 properties -= self._get_permissive(opt_or_descr)
267 #remove global permissive if need
268 self_properties = copy(self._get_properties())
269 if force_permissive is True or 'permissive' in self_properties:
270 properties -= self._get_permissive()
273 if force_properties is not None:
274 self_properties.update(force_properties)
277 properties &= self_properties
278 #mandatory and frozen are special properties
280 properties -= frozenset(('mandatory', 'frozen'))
282 if 'mandatory' in properties and \
283 not self.context.cfgimpl_get_values()._is_empty(opt_or_descr,
285 properties.remove('mandatory')
286 if is_write and 'everything_frozen' in self_properties:
287 properties.add('frozen')
288 elif 'frozen' in properties and not is_write:
289 properties.remove('frozen')
291 if properties != frozenset():
292 props = list(properties)
293 if 'frozen' in properties:
294 raise PropertiesOptionError(_('cannot change the value for '
295 'option {0} this option is frozen'
296 '').format(opt_or_descr._name), props)
298 raise PropertiesOptionError(_("trying to access to an option "
299 "named: {0} with properties {1}"
300 "").format(opt_or_descr._name, str(props)), props)
302 def _get_permissive(self, opt=None):
303 return self._permissives.get(opt, frozenset())
305 def set_permissive(self, permissive, opt=None):
306 if not isinstance(permissive, tuple):
307 raise TypeError(_('permissive must be a tuple'))
308 self._permissives[opt] = frozenset(permissive)
310 #____________________________________________________________
311 def setowner(self, owner):
312 ":param owner: sets the default value for owner at the Config level"
313 if not isinstance(owner, owners.Owner):
314 raise TypeError(_("invalid generic owner {0}").format(str(owner)))
320 #____________________________________________________________
321 def _read(self, remove, append):
328 "convenience method to freeze, hidde and disable"
329 self._read(ro_remove, ro_append)
331 def read_write(self):
332 "convenience method to freeze, hidde and disable"
333 self._read(rw_remove, rw_append)
335 def _set_cache(self, opt, props, exp):
339 self._cache[opt] = (props, time() + expires_time)
341 def reset_cache(self, only_expired):
344 keys = self._cache.keys()
346 props, created = self._cache[key]
348 del(self._cache[key])
352 def apply_requires(self, opt):
353 "carries out the jit (just in time requirements between options"
354 if opt._requires is None:
357 # filters the callbacks
358 setting = Property(self, self._get_properties(opt, False), opt)
359 descr = self.context.cfgimpl_get_description()
360 optpath = descr.impl_get_path_by_opt(opt)
361 for requires in opt._requires:
363 for require in requires:
364 option, expected, action, inverse, transitive, same_action = require
365 path = descr.impl_get_path_by_opt(option)
366 if path == optpath or path.startswith(optpath + '.'):
367 raise RequirementError(_("malformed requirements "
368 "imbrication detected for option: '{0}' "
369 "with requirement on: '{1}'").format(optpath, path))
371 value = self.context._getattr(path, force_permissive=True)
372 except PropertiesOptionError, err:
375 properties = err.proptype
376 if same_action and action not in properties:
377 raise RequirementError(_("option '{0}' has requirement's property error: "
378 "{1} {2}").format(opt._name, path, properties))
379 #transitive action, force expected
382 except AttributeError:
383 raise AttributeError(_("required option not found: "
385 if not inverse and value in expected or inverse and value not in expected:
387 setting.append(action)
388 ## the calculation cannot be carried out
390 # no requirement has been triggered, then just reverse the action
392 setting.remove(action)