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 _
29 default_encoding = 'utf-8'
31 ro_remove = ('permissive', 'hidden')
32 ro_append = ('frozen', 'disabled', 'validator', 'everything_frozen',
34 rw_remove = ('permissive', 'everything_frozen', 'mandatory')
35 rw_append = ('frozen', 'disabled', 'validator', 'hidden')
36 default_properties = ('expire', 'validator')
37 storage_type = 'dictionary'
41 """convenient class that emulates a module
42 and builds constants (that is, unique names)"""
43 class ConstError(TypeError):
46 def __setattr__(self, name, value):
47 if name in self.__dict__:
48 raise self.ConstError, _("can't rebind group ({})").format(name)
49 self.__dict__[name] = value
51 def __delattr__(self, name):
52 if name in self.__dict__:
53 raise self.ConstError, _("can't unbind group ({})").format(name)
54 raise ValueError(name)
57 # ____________________________________________________________
58 class GroupModule(_const):
59 "emulates a module to manage unique group (OptionDescription) names"
61 """allowed normal group (OptionDescription) names
62 *normal* means : groups that are not master
66 class DefaultGroupType(GroupType):
67 """groups that are default (typically 'default')"""
70 class MasterGroupType(GroupType):
71 """allowed normal group (OptionDescription) names
72 *master* means : groups that have the 'master' attribute set
75 # setting.groups (emulates a module)
76 groups = GroupModule()
79 def populate_groups():
80 "populates the available groups in the appropriate namespaces"
81 groups.master = groups.MasterGroupType('master')
82 groups.default = groups.DefaultGroupType('default')
83 groups.family = groups.GroupType('family')
85 # names are in the module now
89 # ____________________________________________________________
90 class OwnerModule(_const):
91 """emulates a module to manage unique owner names.
93 owners are living in `Config._cfgimpl_value_owners`
96 """allowed owner names
100 class DefaultOwner(Owner):
101 """groups that are default (typically 'default')"""
103 # setting.owners (emulates a module)
104 owners = OwnerModule()
107 def populate_owners():
108 """populates the available owners in the appropriate namespaces
110 - 'user' is the generic is the generic owner.
111 - 'default' is the config owner after init time
113 setattr(owners, 'default', owners.DefaultOwner('default'))
114 setattr(owners, 'user', owners.Owner('user'))
118 :param name: the name of the new owner
120 setattr(owners, name, owners.Owner(name))
121 setattr(owners, 'add_owner', add_owner)
123 # names are in the module now
127 class MultiTypeModule(_const):
128 "namespace for the master/slaves"
129 class MultiType(str):
132 class DefaultMultiType(MultiType):
135 class MasterMultiType(MultiType):
138 class SlaveMultiType(MultiType):
141 multitypes = MultiTypeModule()
144 def populate_multitypes():
145 "populates the master/slave namespace"
146 setattr(multitypes, 'default', multitypes.DefaultMultiType('default'))
147 setattr(multitypes, 'master', multitypes.MasterMultiType('master'))
148 setattr(multitypes, 'slave', multitypes.SlaveMultiType('slave'))
150 populate_multitypes()
153 class Property(object):
154 "a property is responsible of the option's value access rules"
155 __slots__ = ('_setting', '_properties', '_opt', '_path')
157 def __init__(self, setting, prop, opt=None, path=None):
160 self._setting = setting
161 self._properties = prop
163 def append(self, propname):
164 self._properties.add(propname)
165 self._setting._setproperties(self._properties, self._opt, self._path)
167 def remove(self, propname):
168 if propname in self._properties:
169 self._properties.remove(propname)
170 self._setting._setproperties(self._properties, self._opt, self._path)
173 self._setting.reset(path=self._path)
175 def __contains__(self, propname):
176 return propname in self._properties
179 return str(list(self._properties))
182 #____________________________________________________________
183 class Settings(object):
184 "``Config()``'s configuration options"
185 __slots__ = ('context', '_owner', '_p_')
187 def __init__(self, context, storage):
191 :param context: the root config
192 :param storage: the storage type
194 - dictionnary -> in memory
195 - sqlite3 -> persistent
198 self._owner = owners.user
199 self.context = context
200 import_lib = 'tiramisu.storage.{0}.setting'.format(storage_type)
201 self._p_ = __import__(import_lib, globals(), locals(), ['Settings'],
202 -1).Settings(storage)
204 #____________________________________________________________
206 def __contains__(self, propname):
207 "enables the pythonic 'in' syntaxic sugar"
208 return propname in self._getproperties()
211 return str(list(self._getproperties()))
213 def __getitem__(self, opt):
217 path = self._get_opt_path(opt)
218 return self._getitem(opt, path)
220 def _getitem(self, opt, path):
221 return Property(self, self._getproperties(opt, path), opt, path)
223 def __setitem__(self, opt, value):
224 raise ValueError('you must only append/remove properties')
226 def reset(self, opt=None, all_properties=False):
227 if all_properties and opt:
228 raise ValueError(_('opt and all_properties must not be set '
229 'together in reset'))
231 self._p_.reset_all_propertives()
236 path = self._get_opt_path(opt)
237 self._p_.reset_properties(path)
238 self.context.cfgimpl_reset_cache()
240 def _getproperties(self, opt=None, path=None, is_apply_req=True):
242 props = self._p_.getproperties(path, default_properties)
245 raise ValueError(_('if opt is not None, path should not be None in _getproperties'))
247 if self._p_.hascache('property', path):
249 is_cached, props = self._p_.getcache('property', path, ntime)
253 self.apply_requires(opt, path)
254 props = self._p_.getproperties(path, opt._properties)
258 self._p_.setcache('property', path, props, ntime + expires_time)
261 def append(self, propname):
262 "puts property propname in the Config's properties attribute"
263 Property(self, self._getproperties()).append(propname)
265 def remove(self, propname):
266 "deletes property propname in the Config's properties attribute"
267 Property(self, self._getproperties()).remove(propname)
269 def _setproperties(self, properties, opt, path):
270 """save properties for specified opt
271 (never save properties if same has option properties)
274 self._p_.setproperties(path, properties)
276 if set(opt._properties) == properties:
277 self._p_.reset_properties(path)
279 self._p_.setproperties(path, properties)
280 self.context.cfgimpl_reset_cache()
282 #____________________________________________________________
283 def validate_properties(self, opt_or_descr, is_descr, is_write, path,
284 value=None, force_permissive=False,
285 force_properties=None):
287 validation upon the properties related to `opt_or_descr`
289 :param opt_or_descr: an option or an option description object
290 :param force_permissive: behaves as if the permissive property was present
291 :param is_descr: we have to know if we are in an option description,
292 just because the mandatory property doesn't exist there
294 :param is_write: in the validation process, an option is to be modified,
295 the behavior can be different (typically with the `frozen`
299 properties = copy(self._getproperties(opt_or_descr, path))
300 # remove opt permissive
301 properties -= self._p_.getpermissive(path)
302 # remove global permissive if need
303 self_properties = copy(self._getproperties())
304 if force_permissive is True or 'permissive' in self_properties:
305 properties -= self._p_.getpermissive()
308 if force_properties is not None:
309 self_properties.update(force_properties)
312 properties &= self_properties
313 # mandatory and frozen are special properties
315 properties -= frozenset(('mandatory', 'frozen'))
317 if 'mandatory' in properties and \
318 not self.context.cfgimpl_get_values()._isempty(
319 opt_or_descr, value):
320 properties.remove('mandatory')
321 if is_write and 'everything_frozen' in self_properties:
322 properties.add('frozen')
323 elif 'frozen' in properties and not is_write:
324 properties.remove('frozen')
325 # at this point an option should not remain in properties
326 if properties != frozenset():
327 props = list(properties)
328 if 'frozen' in properties:
329 raise PropertiesOptionError(_('cannot change the value for '
330 'option {0} this option is'
335 raise PropertiesOptionError(_("trying to access to an option "
336 "named: {0} with properties {1}"
337 "").format(opt_or_descr._name,
340 def setpermissive(self, permissive, path=None):
341 if not isinstance(permissive, tuple):
342 raise TypeError(_('permissive must be a tuple'))
343 self._p_.setpermissive(path, permissive)
345 #____________________________________________________________
346 def setowner(self, owner):
347 ":param owner: sets the default value for owner at the Config level"
348 if not isinstance(owner, owners.Owner):
349 raise TypeError(_("invalid generic owner {0}").format(str(owner)))
355 #____________________________________________________________
356 def _read(self, remove, append):
363 "convenience method to freeze, hidde and disable"
364 self._read(ro_remove, ro_append)
366 def read_write(self):
367 "convenience method to freeze, hidde and disable"
368 self._read(rw_remove, rw_append)
370 def reset_cache(self, only_expired):
372 self._p_.reset_expired_cache('property', time())
374 self._p_.reset_all_cache('property')
376 def apply_requires(self, opt, path):
377 "carries out the jit (just in time requirements between options"
378 if opt._requires is None:
381 # filters the callbacks
382 setting = Property(self, self._getproperties(opt, path, False), opt, path=path)
383 descr = self.context.cfgimpl_get_description()
384 for requires in opt._requires:
386 for require in requires:
387 option, expected, action, inverse, \
388 transitive, same_action = require
389 reqpath = self._get_opt_path(option)
390 if reqpath == path or reqpath.startswith(path + '.'):
391 raise RequirementError(_("malformed requirements "
392 "imbrication detected for option:"
393 " '{0}' with requirement on: "
394 "'{1}'").format(path, reqpath))
396 value = self.context._getattr(reqpath, force_permissive=True)
397 except PropertiesOptionError, err:
400 properties = err.proptype
401 if same_action and action not in properties:
402 raise RequirementError(_("option '{0}' has "
403 "requirement's property "
405 "{1} {2}").format(opt._name,
408 # transitive action, force expected
411 except AttributeError:
412 raise AttributeError(_("required option not found: "
413 "{0}").format(reqpath))
416 inverse and value not in expected):
418 setting.append(action)
419 # the calculation cannot be carried out
421 # no requirement has been triggered, then just reverse the action
423 setting.remove(action)
425 def _get_opt_path(self, opt):
426 return self.context.cfgimpl_get_description().impl_get_path_by_opt(opt)