1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2014 Team tiramisu (see AUTHORS for all contributors)
4 # This program is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU Lesser General Public License as published by the
6 # Free Software Foundation, either version 3 of the License, or (at your
7 # option) any later version.
9 # This program is distributed in the hope that it will be useful, but WITHOUT
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
14 # You should have received a copy of the GNU Lesser General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 # The original `Config` design model is unproudly borrowed from
18 # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
19 # the whole pypy projet is under MIT licence
20 # ____________________________________________________________
26 from ..setting import groups, undefined, owners # , log
27 from .baseoption import BaseOption, SymLinkOption
28 from . import MasterSlaves
29 from ..error import ConfigError, ConflictError
30 from ..storage import get_storages_option
31 from ..autolib import carry_out_calculation
34 StorageOptionDescription = get_storages_option('optiondescription')
36 name_regexp = re.compile(r'^[a-zA-Z\d\-_]*$')
39 if sys.version_info[0] >= 3: # pragma: optional cover
44 class OptionDescription(BaseOption, StorageOptionDescription):
45 """Config's schema (organisation, group) and container of Options
46 The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
50 def __init__(self, name, doc, children, requires=None, properties=None):
52 :param children: a list of options (including optiondescriptions)
55 super(OptionDescription, self).__init__(name, doc=doc,
57 properties=properties,
61 for child in children:
62 name = child.impl_getname()
63 child_names.append(name)
64 if isinstance(child, DynOptionDescription):
65 dynopt_names.append(name)
67 #better performance like this
68 valid_child = copy(child_names)
71 for child in valid_child:
72 if child == old: # pragma: optional cover
73 raise ConflictError(_('duplicate option name: '
76 for dynopt in dynopt_names:
77 if child != dynopt and child.startswith(dynopt):
78 raise ConflictError(_('option must not start as '
79 'dynoptiondescription'))
81 self._add_children(child_names, children)
82 _setattr = object.__setattr__
83 _setattr(self, '_cache_consistencies', None)
84 # the group_type is useful for filtering OptionDescriptions in a config
85 _setattr(self, '_group_type', groups.default)
87 def impl_getdoc(self):
88 return self.impl_get_information('doc')
90 def impl_validate(self, *args, **kwargs):
91 """usefull for OptionDescription"""
94 def impl_getpaths(self, include_groups=False, _currpath=None):
95 """returns a list of all paths in self, recursively
96 _currpath should not be provided (helps with recursion)
98 return _impl_getpaths(self, include_groups, _currpath)
100 def impl_build_cache(self, config, path='', _consistencies=None,
101 cache_option=None, force_store_values=None):
102 """validate duplicate option and set option has readonly option
104 if cache_option is None:
108 force_store_values = []
111 for option in self._impl_getchildren(dyn=False):
112 #FIXME specifique id for sqlalchemy?
113 #FIXME avec sqlalchemy ca marche le multi parent ? (dans des configs différentes)
114 cache_option.append(option._get_id())
116 subpath = option.impl_getname()
118 subpath = path + '.' + option.impl_getname()
119 if isinstance(option, OptionDescription):
120 option._set_readonly(False)
121 option.impl_build_cache(config, subpath, _consistencies,
122 cache_option, force_store_values)
123 #cannot set multi option as OptionDescription requires
125 option._set_readonly(True)
126 is_multi = option.impl_is_multi()
127 if not isinstance(option, SymLinkOption) and 'force_store_value' in option.impl_getproperties():
128 force_store_values.append((subpath, option))
129 for func, all_cons_opts, params in option._get_consistencies():
130 option._valid_consistencies(all_cons_opts[1:])
132 is_slave = option.impl_is_master_slaves()
134 raise ValueError(_('malformed consistency option {0} '
135 'must be a master/slaves').format(
136 option.impl_getname()))
137 masterslaves = option.impl_get_master_slaves()
138 for opt in all_cons_opts:
140 if not opt.impl_is_master_slaves():
141 raise ValueError(_('malformed consistency option {0} '
142 'must not be a multi for {1}').format(
143 option.impl_getname(), opt.impl_getname()))
144 elif masterslaves != opt.impl_get_master_slaves():
145 raise ValueError(_('malformed consistency option {0} '
146 'must be in same master/slaves for {1}').format(
147 option.impl_getname(), opt.impl_getname()))
148 _consistencies.setdefault(opt,
154 all_requires = option.impl_getrequires()
155 if all_requires != tuple():
156 for requires in all_requires:
157 for require in requires:
158 #if option in require is a multi:
159 # * option in require must be a master or a slave
160 # * current option must be a slave (and only a slave)
161 # * option in require and current option must be in same master/slaves
162 require_opt = require[0]
163 if require_opt.impl_is_multi():
165 is_slave = option.impl_is_master_slaves('slave')
167 masterslaves = option.impl_get_master_slaves()
168 if is_slave and require_opt.impl_is_master_slaves():
169 if masterslaves != require_opt.impl_get_master_slaves():
170 raise ValueError(_('malformed requirements option {0} '
171 'must be in same master/slaves for {1}').format(
172 require_opt.impl_getname(), option.impl_getname()))
174 raise ValueError(_('malformed requirements option {0} '
175 'must not be a multi for {1}').format(
176 require_opt.impl_getname(), option.impl_getname()))
178 session = config._impl_values._p_.getsession()
179 if len(cache_option) != len(set(cache_option)):
180 for idx in xrange(1, len(cache_option) + 1):
181 opt = cache_option.pop(0)
182 if opt in cache_option:
183 raise ConflictError(_('duplicate option: {0}').format(opt))
184 if _consistencies != {}:
185 self._cache_consistencies = {}
186 for opt, cons in _consistencies.items():
187 if opt._get_id() not in cache_option: # pragma: optional cover
188 raise ConfigError(_('consistency with option {0} '
189 'which is not in Config').format(
191 self._cache_consistencies[opt] = tuple(cons)
192 self._set_readonly(False)
193 for subpath, option in force_store_values:
194 value = config.cfgimpl_get_values()._get_cached_value(option,
197 trusted_cached_properties=False,
198 validate_properties=True)
199 if option.impl_is_master_slaves('slave'):
201 raise ConfigError(_('a slave ({0}) cannot have '
202 'force_store_value property').format(subpath))
203 if option._is_subdyn():
204 raise ConfigError(_('a dynoption ({0}) cannot have '
205 'force_store_value property').format(subpath))
206 config._impl_values._p_.setvalue(subpath, value,
207 owners.forced, None, session)
210 # ____________________________________________________________
211 def impl_set_group_type(self, group_type):
212 """sets a given group object to an OptionDescription
214 :param group_type: an instance of `GroupType` or `MasterGroupType`
215 that lives in `setting.groups`
217 if self._group_type != groups.default: # pragma: optional cover
218 raise TypeError(_('cannot change group_type if already set '
219 '(old {0}, new {1})').format(self._group_type,
221 if isinstance(group_type, groups.GroupType):
222 self._group_type = group_type
223 if isinstance(group_type, groups.MasterGroupType):
224 MasterSlaves(self.impl_getname(), self.impl_getchildren())
225 else: # pragma: optional cover
226 raise ValueError(_('group_type: {0}'
227 ' not allowed').format(group_type))
229 def _impl_getstate(self, descr=None):
230 """enables us to export into a dict
231 :param descr: parent :class:`tiramisu.option.OptionDescription`
234 self.impl_build_cache_option()
236 super(OptionDescription, self)._impl_getstate(descr)
237 self._state_group_type = str(self._group_type)
238 for option in self._impl_getchildren():
239 option._impl_getstate(descr)
241 def __getstate__(self):
242 """special method to enable the serialization with pickle
246 # the `_state` attribute is a flag that which tells us if
247 # the serialization can be performed
249 except AttributeError:
250 # if cannot delete, _impl_getstate never launch
251 # launch it recursivement
252 # _stated prevent __getstate__ launch more than one time
253 # _stated is delete, if re-serialize, re-lauch _impl_getstate
254 self._impl_getstate()
256 return super(OptionDescription, self).__getstate__(stated)
258 def _impl_setstate(self, descr=None):
259 """enables us to import from a dict
260 :param descr: parent :class:`tiramisu.option.OptionDescription`
263 self._cache_consistencies = None
264 self.impl_build_cache_option()
266 self._group_type = getattr(groups, self._state_group_type)
267 if isinstance(self._group_type, groups.MasterGroupType):
268 MasterSlaves(self.impl_getname(), self.impl_getchildren(),
270 del(self._state_group_type)
271 super(OptionDescription, self)._impl_setstate(descr)
272 for option in self._impl_getchildren(dyn=False):
273 option._impl_setstate(descr)
275 def __setstate__(self, state):
276 super(OptionDescription, self).__setstate__(state)
279 except AttributeError:
280 self._impl_setstate()
282 def _impl_get_suffixes(self, context):
283 callback, callback_params = self.impl_get_callback()
284 values = carry_out_calculation(self, context=context,
286 callback_params=callback_params)
287 if len(values) > len(set(values)):
288 raise ConfigError(_('DynOptionDescription callback return not uniq value'))
290 if not isinstance(val, str) or re.match(name_regexp, val) is None:
291 raise ValueError(_("invalid suffix: {0} for option").format(val))
294 def _impl_search_dynchild(self, name=undefined, context=undefined):
296 for child in self._impl_st_getchildren(context, only_dyn=True):
297 cname = child.impl_getname()
298 if name is undefined or name.startswith(cname):
300 for value in child._impl_get_suffixes(context):
301 if name is undefined:
302 ret.append(SynDynOptionDescription(child, cname + value, path + value, value))
303 elif name == cname + value:
304 return SynDynOptionDescription(child, name, path + value, value)
307 def _impl_get_dynchild(self, child, suffix):
308 name = child.impl_getname() + suffix
309 path = self.impl_getname() + suffix + '.' + name
310 if isinstance(child, OptionDescription):
311 return SynDynOptionDescription(child, name, path, suffix)
313 return child._impl_to_dyn(name, path)
315 def _impl_getchildren(self, dyn=True, context=undefined):
316 for child in self._impl_st_getchildren(context):
317 cname = child.impl_getname()
318 if dyn and child.impl_is_dynoptiondescription():
320 for value in child._impl_get_suffixes(context):
321 yield SynDynOptionDescription(child,
327 def impl_getchildren(self):
328 return list(self._impl_getchildren())
330 def __getattr__(self, name, context=undefined):
331 if name.startswith('_'): # or name.startswith('impl_'):
332 return object.__getattribute__(self, name)
334 path = name.split('.')[0]
335 subpath = '.'.join(name.split('.')[1:])
336 return self.__getattr__(path, context=context).__getattr__(subpath, context=context)
337 return self._getattr(name, context=context)
340 class DynOptionDescription(OptionDescription):
341 def __init__(self, name, doc, children, requires=None, properties=None,
342 callback=None, callback_params=None):
343 super(DynOptionDescription, self).__init__(name, doc, children,
344 requires, properties)
345 for child in children:
346 if isinstance(child, OptionDescription):
347 if child.impl_get_group_type() != groups.master:
348 raise ConfigError(_('cannot set optiondescription in a '
349 'dynoptiondescription'))
350 for chld in child._impl_getchildren():
351 chld._impl_setsubdyn(self)
352 if isinstance(child, SymLinkOption):
353 raise ConfigError(_('cannot set symlinkoption in a '
354 'dynoptiondescription'))
355 if isinstance(child, SymLinkOption):
356 raise ConfigError(_('cannot set symlinkoption in a '
357 'dynoptiondescription'))
358 child._impl_setsubdyn(self)
359 self.impl_set_callback(callback, callback_params)
362 def _validate_callback(self, callback, callback_params):
364 raise ConfigError(_('callback is mandatory for dynoptiondescription'))
367 class SynDynOptionDescription(object):
368 __slots__ = ('_opt', '_name', '_path', '_suffix')
370 def __init__(self, opt, name, path, suffix):
374 self._suffix = suffix
376 def __getattr__(self, name, context=undefined):
377 if name in dir(self._opt):
378 return getattr(self._opt, name)
379 return self._opt._getattr(name, suffix=self._suffix, context=context)
381 def impl_getname(self):
384 def _impl_getchildren(self, dyn=True, context=undefined):
386 for child in self._opt._impl_getchildren():
387 children.append(self._opt._impl_get_dynchild(child, self._suffix))
390 def impl_getchildren(self):
391 return self._impl_getchildren()
393 def impl_getpath(self, context):
396 def impl_getpaths(self, include_groups=False, _currpath=None):
397 return _impl_getpaths(self, include_groups, _currpath)
399 def _impl_getopt(self):
403 def _impl_getpaths(klass, include_groups, _currpath):
404 """returns a list of all paths in klass, recursively
405 _currpath should not be provided (helps with recursion)
407 if _currpath is None:
410 for option in klass._impl_getchildren():
411 attr = option.impl_getname()
412 if option.impl_is_optiondescription():
414 paths.append('.'.join(_currpath + [attr]))
415 paths += option.impl_getpaths(include_groups=include_groups,
416 _currpath=_currpath + [attr])
418 paths.append('.'.join(_currpath + [attr]))