56a173288866bdf8ad0a9103d8b52affdd4b1005
[tiramisu.git] / tiramisu / config.py
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors)
3 #
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.
8 #
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
12 # details.
13 #
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/>.
16 #
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 # ____________________________________________________________
21 "options handler global entry point"
22 import weakref
23 from tiramisu.error import PropertiesOptionError, ConfigError, ConflictError
24 from tiramisu.option import OptionDescription, Option, SymLinkOption, \
25     DynSymLinkOption
26 from tiramisu.option.baseoption import valid_name
27 from tiramisu.setting import groups, Settings, default_encoding, undefined
28 from tiramisu.storage import get_storages, get_storage, set_storage, \
29     _impl_getstate_setting
30 from tiramisu.value import Values, Multi
31 from tiramisu.i18n import _
32
33
34 class SubConfig(object):
35     """Sub configuration management entry.
36     Tree if OptionDescription's responsability. SubConfig are generated
37     on-demand. A Config is also a SubConfig.
38     Root Config is call context below
39     """
40     __slots__ = ('_impl_context', '_impl_descr', '_impl_path')
41
42     def __init__(self, descr, context, subpath=None):
43         """ Configuration option management master class
44
45         :param descr: describes the configuration schema
46         :type descr: an instance of ``option.OptionDescription``
47         :param context: the current root config
48         :type context: `Config`
49         :type subpath: `str` with the path name
50         """
51         # main option description
52         error = False
53         try:
54             if descr is not None and not descr.impl_is_optiondescription():  # pragma: optional cover
55                 error = True
56         except AttributeError:
57             error = True
58         if error:
59             raise TypeError(_('descr must be an optiondescription, not {0}'
60                               ).format(type(descr)))
61         self._impl_descr = descr
62         # sub option descriptions
63         if not isinstance(context, weakref.ReferenceType):  # pragma: optional cover
64             raise ValueError('context must be a Weakref')
65         self._impl_context = context
66         self._impl_path = subpath
67
68     def cfgimpl_reset_cache(self, only_expired=False, only=('values',
69                                                             'settings')):
70         "remove cache (in context)"
71         self._cfgimpl_get_context().cfgimpl_reset_cache(only_expired, only)  # pragma: optional cover
72
73     def cfgimpl_get_home_by_path(self, path, force_permissive=False):
74         """:returns: tuple (config, name)"""
75         path = path.split('.')
76         for step in path[:-1]:
77             self = self.getattr(step,
78                                 force_permissive=force_permissive)
79         return self, path[-1]
80
81     #def __hash__(self):
82     #FIXME
83     #    return hash(self.cfgimpl_get_description().impl_getkey(self))
84
85     #def __eq__(self, other):
86     #FIXME
87     #    "Config's comparison"
88     #    if not isinstance(other, Config):
89     #        return False
90     #    return self.cfgimpl_get_description().impl_getkey(self) == \
91     #        other.cfgimpl_get_description().impl_getkey(other)
92
93     #def __ne__(self, other):
94     #FIXME
95     #    "Config's comparison"
96     #    if not isinstance(other, Config):
97     #        return True
98     #    return not self == other
99
100     # ______________________________________________________________________
101     def __iter__(self, force_permissive=False):
102         """Pythonesque way of parsing group's ordered options.
103         iteration only on Options (not OptionDescriptions)"""
104         for child in self.cfgimpl_get_description()._impl_getchildren(
105                 context=self._cfgimpl_get_context()):
106             if not child.impl_is_optiondescription():
107                 try:
108                     name = child.impl_getname()
109                     yield name, self.getattr(name,
110                                              force_permissive=force_permissive)
111                 except GeneratorExit:  # pragma: optional cover
112                     raise StopIteration
113                 except PropertiesOptionError:  # pragma: optional cover
114                     pass  # option with properties
115
116     def iter_all(self, force_permissive=False):
117         """A way of parsing options **and** groups.
118         iteration on Options and OptionDescriptions."""
119         for child in self.cfgimpl_get_description().impl_getchildren():
120             try:
121                 yield child.impl_getname(), self.getattr(child.impl_getname(),
122                                                          force_permissive=force_permissive)
123             except GeneratorExit:  # pragma: optional cover
124                 raise StopIteration
125             except PropertiesOptionError:  # pragma: optional cover
126                 pass  # option with properties
127
128     def iter_groups(self, group_type=None, force_permissive=False):
129         """iteration on groups objects only.
130         All groups are returned if `group_type` is `None`, otherwise the groups
131         can be filtered by categories (families, or whatever).
132
133         :param group_type: if defined, is an instance of `groups.GroupType`
134                            or `groups.MasterGroupType` that lives in
135                            `setting.groups`
136         """
137         if group_type is not None and not isinstance(group_type,
138                                                      groups.GroupType):  # pragma: optional cover
139             raise TypeError(_("unknown group_type: {0}").format(group_type))
140         for child in self.cfgimpl_get_description()._impl_getchildren(
141                 context=self._cfgimpl_get_context()):
142             if child.impl_is_optiondescription():
143                 try:
144                     if group_type is None or (group_type is not None and
145                                               child.impl_get_group_type()
146                                               == group_type):
147                         name = child.impl_getname()
148                         yield name, self.getattr(name, force_permissive=force_permissive)
149                 except GeneratorExit:  # pragma: optional cover
150                     raise StopIteration
151                 except PropertiesOptionError:  # pragma: optional cover
152                     pass
153     # ______________________________________________________________________
154
155     def __str__(self):
156         "Config's string representation"
157         lines = []
158         for name, grp in self.iter_groups():
159             lines.append("[{0}]".format(name))
160         for name, value in self:
161             try:
162                 lines.append("{0} = {1}".format(name, value))
163             except UnicodeEncodeError:  # pragma: optional cover
164                 lines.append("{0} = {1}".format(name,
165                                                 value.encode(default_encoding)))
166         return '\n'.join(lines)
167
168     __repr__ = __str__
169
170     def _cfgimpl_get_context(self):
171         """context could be None, we need to test it
172         context is None only if all reference to `Config` object is deleted
173         (for example we delete a `Config` and we manipulate a reference to
174         old `SubConfig`, `Values`, `Multi` or `Settings`)
175         """
176         context = self._impl_context()
177         if context is None:  # pragma: optional cover
178             raise ConfigError(_('the context does not exist anymore'))
179         return context
180
181     def cfgimpl_get_description(self):
182         if self._impl_descr is None:  # pragma: optional cover
183             raise ConfigError(_('no option description found for this config'
184                                 ' (may be GroupConfig)'))
185         else:
186             return self._impl_descr
187
188     def cfgimpl_get_settings(self):
189         return self._cfgimpl_get_context()._impl_settings
190
191     def cfgimpl_get_values(self):
192         return self._cfgimpl_get_context()._impl_values
193
194     # ____________________________________________________________
195     # attribute methods
196     def __setattr__(self, name, value):
197         "attribute notation mechanism for the setting of the value of an option"
198         self._setattr(name, value)
199
200     def _setattr(self, name, value, force_permissive=False):
201         if name.startswith('_impl_'):
202             object.__setattr__(self, name, value)
203             return
204         if '.' in name:  # pragma: optional cover
205             homeconfig, name = self.cfgimpl_get_home_by_path(name)
206             return homeconfig._setattr(name, value, force_permissive)
207         context = self._cfgimpl_get_context()
208         child = self.cfgimpl_get_description().__getattr__(name,
209                                                            context=context)
210         if isinstance(child, OptionDescription):
211             raise TypeError(_("can't assign to an OptionDescription"))  # pragma: optional cover
212         elif isinstance(child, SymLinkOption) and \
213                 not isinstance(child, DynSymLinkOption):  # pragma: no dynoptiondescription cover
214             path = context.cfgimpl_get_description().impl_get_path_by_opt(
215                 child._impl_getopt())
216             context._setattr(path, value, force_permissive=force_permissive)
217         else:
218             subpath = self._get_subpath(name)
219             self.cfgimpl_get_values().setitem(child, value, subpath,
220                                               force_permissive=force_permissive)
221
222     def __delattr__(self, name):
223         context = self._cfgimpl_get_context()
224         child = self.cfgimpl_get_description().__getattr__(name, context)
225         self.cfgimpl_get_values().__delitem__(child)
226
227     def __getattr__(self, name):
228         return self.getattr(name)
229
230     def _getattr(self, name, force_permissive=False, validate=True):  # pragma: optional cover
231         """use getattr instead of _getattr
232         """
233         return self.getattr(name, force_permissive, validate)
234
235     def _get_subpath(self, name):
236         if self._impl_path is None:
237             subpath = name
238         else:
239             subpath = self._impl_path + '.' + name
240         return subpath
241
242     def getattr(self, name, force_permissive=False, validate=True):
243         """
244         attribute notation mechanism for accessing the value of an option
245         :param name: attribute name
246         :return: option's value if name is an option name, OptionDescription
247                  otherwise
248         """
249         # attribute access by passing a path,
250         # for instance getattr(self, "creole.general.family.adresse_ip_eth0")
251         if '.' in name:
252             homeconfig, name = self.cfgimpl_get_home_by_path(
253                 name, force_permissive=force_permissive)
254             return homeconfig.getattr(name, force_permissive=force_permissive,
255                                       validate=validate)
256         context = self._cfgimpl_get_context()
257         opt_or_descr = self.cfgimpl_get_description().__getattr__(
258             name, context=context)
259         subpath = self._get_subpath(name)
260         if isinstance(opt_or_descr, DynSymLinkOption):
261             return self.cfgimpl_get_values()._get_cached_item(
262                 opt_or_descr, path=subpath,
263                 validate=validate,
264                 force_permissive=force_permissive)
265         elif isinstance(opt_or_descr, SymLinkOption):  # pragma: no dynoptiondescription cover
266             path = context.cfgimpl_get_description().impl_get_path_by_opt(
267                 opt_or_descr._impl_getopt())
268             return context.getattr(path, validate=validate,
269                                    force_permissive=force_permissive)
270         elif opt_or_descr.impl_is_optiondescription():
271             self.cfgimpl_get_settings().validate_properties(
272                 opt_or_descr, True, False, path=subpath,
273                 force_permissive=force_permissive)
274             return SubConfig(opt_or_descr, self._impl_context, subpath)
275         else:
276             return self.cfgimpl_get_values()._get_cached_item(
277                 opt_or_descr, path=subpath,
278                 validate=validate,
279                 force_permissive=force_permissive)
280
281     def find(self, bytype=None, byname=None, byvalue=undefined, type_='option',
282              check_properties=True, force_permissive=False):
283         """
284             finds a list of options recursively in the config
285
286             :param bytype: Option class (BoolOption, StrOption, ...)
287             :param byname: filter by Option.impl_getname()
288             :param byvalue: filter by the option's value
289             :returns: list of matching Option objects
290         """
291         return self._cfgimpl_get_context()._find(bytype, byname, byvalue,
292                                                  first=False,
293                                                  type_=type_,
294                                                  _subpath=self.cfgimpl_get_path(False),
295                                                  check_properties=check_properties,
296                                                  force_permissive=force_permissive)
297
298     def find_first(self, bytype=None, byname=None, byvalue=undefined,
299                    type_='option', display_error=True, check_properties=True,
300                    force_permissive=False):
301         """
302             finds an option recursively in the config
303
304             :param bytype: Option class (BoolOption, StrOption, ...)
305             :param byname: filter by Option.impl_getname()
306             :param byvalue: filter by the option's value
307             :returns: list of matching Option objects
308         """
309         return self._cfgimpl_get_context()._find(
310             bytype, byname, byvalue, first=True, type_=type_,
311             _subpath=self.cfgimpl_get_path(False), display_error=display_error,
312             check_properties=check_properties,
313             force_permissive=force_permissive)
314
315     def _find(self, bytype, byname, byvalue, first, type_='option',
316               _subpath=None, check_properties=True, display_error=True,
317               force_permissive=False, only_path=undefined,
318               only_option=undefined):
319         """
320         convenience method for finding an option that lives only in the subtree
321
322         :param first: return only one option if True, a list otherwise
323         :return: find list or an exception if nothing has been found
324         """
325
326         def _filter_by_value():
327             if byvalue is undefined:
328                 return True
329             try:
330                 value = self.getattr(path, force_permissive=force_permissive)
331                 if isinstance(value, Multi):
332                     return byvalue in value
333                 else:
334                     return value == byvalue
335             # a property is a restriction upon the access of the value
336             except PropertiesOptionError:  # pragma: optional cover
337                 return False
338
339         if type_ not in ('option', 'path', 'value'):  # pragma: optional cover
340             raise ValueError(_('unknown type_ type {0}'
341                                'for _find').format(type_))
342         find_results = []
343         # if value and/or check_properties are set, need all avalaible option
344         # If first one has no good value or not good property check second one
345         # and so on
346         only_first = first is True and byvalue is None and \
347             check_properties is None
348         if only_path is not undefined:
349             options = [(only_path, only_option)]
350         else:
351             options = self.cfgimpl_get_description().impl_get_options_paths(
352                 bytype, byname, _subpath, only_first,
353                 self._cfgimpl_get_context())
354         for path, option in options:
355             if not _filter_by_value():
356                 continue
357             #remove option with propertyerror, ...
358             if byvalue is undefined and check_properties:
359                 try:
360                     value = self.getattr(path,
361                                          force_permissive=force_permissive)
362                 except PropertiesOptionError:  # pragma: optional cover
363                     # a property restricts the access of the value
364                     continue
365             if type_ == 'value':
366                 retval = value
367             elif type_ == 'path':
368                 retval = path
369             elif type_ == 'option':
370                 retval = option
371             if first:
372                 return retval
373             else:
374                 find_results.append(retval)
375         return self._find_return_results(find_results, display_error)
376
377     def _find_return_results(self, find_results, display_error):
378         if find_results == []:  # pragma: optional cover
379             if display_error:
380                 raise AttributeError(_("no option found in config"
381                                        " with these criteria"))
382             else:
383                 # translation is slow
384                 raise AttributeError()
385         else:
386             return find_results
387
388     def make_dict(self, flatten=False, _currpath=None, withoption=None,
389                   withvalue=undefined, force_permissive=False):
390         """exports the whole config into a `dict`, for example:
391
392         >>> print cfg.make_dict()
393         {'od2.var4': None, 'od2.var5': None, 'od2.var6': None}
394
395
396
397         :param flatten: returns a dict(name=value) instead of a dict(path=value)
398                         ::
399
400                             >>> print cfg.make_dict(flatten=True)
401                             {'var5': None, 'var4': None, 'var6': None}
402
403         :param withoption: returns the options that are present in the very same
404                            `OptionDescription` than the `withoption` itself::
405
406                                 >>> print cfg.make_dict(withoption='var1')
407                                 {'od2.var4': None, 'od2.var5': None,
408                                 'od2.var6': None,
409                                 'od2.var1': u'value',
410                                 'od1.var1': None,
411                                 'od1.var3': None,
412                                 'od1.var2': None}
413
414         :param withvalue: returns the options that have the value `withvalue`
415                           ::
416
417                             >>> print c.make_dict(withoption='var1',
418                                                   withvalue=u'value')
419                             {'od2.var4': None,
420                             'od2.var5': None,
421                             'od2.var6': None,
422                             'od2.var1': u'value'}
423
424         :returns: dict of Option's name (or path) and values
425         """
426         pathsvalues = []
427         if _currpath is None:
428             _currpath = []
429         if withoption is None and withvalue is not undefined:  # pragma: optional cover
430             raise ValueError(_("make_dict can't filtering with value without "
431                                "option"))
432         if withoption is not None:
433             context = self._cfgimpl_get_context()
434             for path in context._find(bytype=None, byname=withoption,
435                                       byvalue=withvalue, first=False,
436                                       type_='path', _subpath=self.cfgimpl_get_path(False),
437                                       force_permissive=force_permissive):
438                 path = '.'.join(path.split('.')[:-1])
439                 opt = context.unwrap_from_path(path, force_permissive=True)
440                 mypath = self.cfgimpl_get_path()
441                 if mypath is not None:
442                     if mypath == path:
443                         withoption = None
444                         withvalue = undefined
445                         break
446                     else:
447                         tmypath = mypath + '.'
448                         if not path.startswith(tmypath):  # pragma: optional cover
449                             raise AttributeError(_('unexpected path {0}, '
450                                                    'should start with {1}'
451                                                    '').format(path, mypath))
452                         path = path[len(tmypath):]
453                 self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten,
454                                     force_permissive=force_permissive)
455         #withoption can be set to None below !
456         if withoption is None:
457             for opt in self.cfgimpl_get_description().impl_getchildren():
458                 path = opt.impl_getname()
459                 self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten,
460                                     force_permissive=force_permissive)
461         if _currpath == []:
462             options = dict(pathsvalues)
463             return options
464         return pathsvalues
465
466     def _make_sub_dict(self, opt, path, pathsvalues, _currpath, flatten,
467                        force_permissive=False):
468         try:
469             if opt.impl_is_optiondescription():
470                 pathsvalues += self.getattr(path,
471                                             force_permissive=force_permissive).make_dict(
472                                                 flatten,
473                                                 _currpath + path.split('.'),
474                                                 force_permissive=force_permissive)
475             else:
476                 value = self.getattr(opt.impl_getname(),
477                                      force_permissive=force_permissive)
478                 if flatten:
479                     name = opt.impl_getname()
480                 else:
481                     name = '.'.join(_currpath + [opt.impl_getname()])
482                 pathsvalues.append((name, value))
483         except PropertiesOptionError:  # pragma: optional cover
484             pass
485
486     def cfgimpl_get_path(self, dyn=True):
487         descr = self.cfgimpl_get_description()
488         if not dyn and descr.impl_is_dynoptiondescription():
489             context_descr = self._cfgimpl_get_context().cfgimpl_get_description()
490             return context_descr.impl_get_path_by_opt(descr._impl_getopt())
491         return self._impl_path
492
493
494 class _CommonConfig(SubConfig):
495     "abstract base class for the Config, GroupConfig and the MetaConfig"
496     __slots__ = ('_impl_values', '_impl_settings', '_impl_meta', '_impl_test')
497
498     def _impl_build_all_caches(self):
499         if not self.cfgimpl_get_description().impl_already_build_caches():
500             self.cfgimpl_get_description().impl_build_cache_consistency()
501             self.cfgimpl_get_description().impl_build_cache_option()
502             self.cfgimpl_get_description().impl_validate_options()
503
504     def read_only(self):
505         "read only is a global config's setting, see `settings.py`"
506         self.cfgimpl_get_settings().read_only()
507
508     def read_write(self):
509         "read write is a global config's setting, see `settings.py`"
510         self.cfgimpl_get_settings().read_write()
511
512     def getowner(self, opt, force_permissive=False):
513         """convenience method to retrieve an option's owner
514         from the config itself
515         """
516         if not isinstance(opt, Option) and \
517                 not isinstance(opt, SymLinkOption) and \
518                 not isinstance(opt, DynSymLinkOption):  # pragma: optional cover
519             raise TypeError(_('opt in getowner must be an option not {0}'
520                               '').format(type(opt)))
521         return self.cfgimpl_get_values().getowner(opt,
522                                                   force_permissive=force_permissive)
523
524     def unwrap_from_path(self, path, force_permissive=False):
525         """convenience method to extract and Option() object from the Config()
526         and it is **fast**: finds the option directly in the appropriate
527         namespace
528
529         :returns: Option()
530         """
531         context = self._cfgimpl_get_context()
532         if '.' in path:
533             homeconfig, path = self.cfgimpl_get_home_by_path(
534                 path, force_permissive=force_permissive)
535             return homeconfig.cfgimpl_get_description().__getattr__(path, context=context)
536         return self.cfgimpl_get_description().__getattr__(path, context=context)
537
538     def cfgimpl_get_path(self, dyn=True):
539         return None
540
541     def cfgimpl_get_meta(self):
542         if self._impl_meta is not None:
543             return self._impl_meta()
544
545     # information
546     def impl_set_information(self, key, value):
547         """updates the information's attribute
548
549         :param key: information's key (ex: "help", "doc"
550         :param value: information's value (ex: "the help string")
551         """
552         self._impl_values.set_information(key, value)
553
554     def impl_get_information(self, key, default=undefined):
555         """retrieves one information's item
556
557         :param key: the item string (ex: "help")
558         """
559         return self._impl_values.get_information(key, default)
560
561     # ----- state
562     def __getstate__(self):
563         if self._impl_meta is not None:
564             raise ConfigError(_('cannot serialize Config with MetaConfig'))  # pragma: optional cover
565         slots = set()
566         for subclass in self.__class__.__mro__:
567             if subclass is not object:
568                 slots.update(subclass.__slots__)
569         slots -= frozenset(['_impl_context', '_impl_meta', '__weakref__'])
570         state = {}
571         for slot in slots:
572             try:
573                 state[slot] = getattr(self, slot)
574             except AttributeError:  # pragma: optional cover
575                 pass
576         storage = self._impl_values._p_._storage
577         if not storage.serializable:
578             raise ConfigError(_('this storage is not serialisable, could be a '
579                               'none persistent storage'))  # pragma: optional cover
580         state['_storage'] = {'session_id': storage.session_id,
581                              'persistent': storage.persistent}
582         state['_impl_setting'] = _impl_getstate_setting()
583         return state
584
585     def __setstate__(self, state):
586         for key, value in state.items():
587             if key not in ['_storage', '_impl_setting']:
588                 setattr(self, key, value)
589         set_storage('config', **state['_impl_setting'])
590         self._impl_context = weakref.ref(self)
591         self._impl_settings.context = weakref.ref(self)
592         self._impl_values.context = weakref.ref(self)
593         storage = get_storage('config', test=self._impl_test, **state['_storage'])
594         self._impl_values._impl_setstate(storage)
595         self._impl_settings._impl_setstate(storage)
596         self._impl_meta = None
597
598
599 # ____________________________________________________________
600 class Config(_CommonConfig):
601     "main configuration management entry"
602     __slots__ = ('__weakref__', '_impl_test', '_impl_name')
603
604     def __init__(self, descr, session_id=None, persistent=False,
605                  name=undefined):
606         """ Configuration option management master class
607
608         :param descr: describes the configuration schema
609         :type descr: an instance of ``option.OptionDescription``
610         :param context: the current root config
611         :type context: `Config`
612         :param session_id: session ID is import with persistent Config to
613         retrieve good session
614         :type session_id: `str`
615         :param persistent: if persistent, don't delete storage when leaving
616         :type persistent: `boolean`
617         """
618         settings, values = get_storages(self, session_id, persistent)
619         self._impl_settings = Settings(self, settings)
620         self._impl_values = Values(self, values)
621         super(Config, self).__init__(descr, weakref.ref(self))
622         self._impl_build_all_caches()
623         self._impl_meta = None
624         #undocumented option used only in test script
625         self._impl_test = False
626         if name is undefined:
627             name = 'config'
628             if session_id is not None:
629                 name += session_id
630         if name is not None and not valid_name(name):  # pragma: optional cover
631             raise ValueError(_("invalid name: {0} for config").format(name))
632         self._impl_name = name
633
634     def cfgimpl_reset_cache(self,
635                             only_expired=False,
636                             only=('values', 'settings')):
637         if 'values' in only:
638             self.cfgimpl_get_values().reset_cache(only_expired=only_expired)
639         if 'settings' in only:
640             self.cfgimpl_get_settings().reset_cache(only_expired=only_expired)
641
642     def impl_getname(self):
643         return self._impl_name
644
645     def impl_setname(self, name):
646         self._impl_name = name
647
648
649 class GroupConfig(_CommonConfig):
650     __slots__ = ('__weakref__', '_impl_children', '_impl_name')
651
652     def __init__(self, children, session_id=None, persistent=False,
653                  _descr=None, name=undefined):
654         if not isinstance(children, list):
655             raise ValueError(_("groupconfig's children must be a list"))
656         names = []
657         for child in children:
658             if not isinstance(child, _CommonConfig):
659                 raise ValueError(_("groupconfig's children must be Config, MetaConfig or GroupConfig"))
660             name = child._impl_name
661             if name is None:
662                 raise ValueError(_('name must be set to config before creating groupconfig'))
663             #if name in names:
664             #    raise ValueError(_('config name must be uniq in groupconfig'))
665             names.append(name)
666         if len(names) != len(set(names)):
667             for idx in xrange(1, len(names) + 1):
668                 name = names.pop(0)
669                 if name in names:
670                     raise ConflictError(_('config name must be uniq in '
671                                           'groupconfig for {0}').format(name))
672         self._impl_children = children
673         settings, values = get_storages(self, session_id, persistent)
674         self._impl_settings = Settings(self, settings)
675         self._impl_values = Values(self, values)
676         super(GroupConfig, self).__init__(_descr, weakref.ref(self))
677         self._impl_meta = None
678         #undocumented option used only in test script
679         self._impl_test = False
680         if name is undefined:
681             name = session_id
682         self._impl_name = name
683
684     def cfgimpl_get_children(self):
685         return self._impl_children
686
687     #def cfgimpl_get_context(self):
688     #    "a meta config is a config which has a setting, that is itself"
689     #    return self
690
691     def cfgimpl_reset_cache(self,
692                             only_expired=False,
693                             only=('values', 'settings')):
694         if 'values' in only:
695             self.cfgimpl_get_values().reset_cache(only_expired=only_expired)
696         if 'settings' in only:
697             self.cfgimpl_get_settings().reset_cache(only_expired=only_expired)
698         for child in self._impl_children:
699             child.cfgimpl_reset_cache(only_expired=only_expired, only=only)
700
701     def set_value(self, path, value):
702         """Setattr not in current GroupConfig, but in each children
703         """
704         for child in self._impl_children:
705             try:
706                 if isinstance(child, MetaConfig):
707                     child.set_value(path, value, only_config=True)
708                 elif isinstance(child, GroupConfig):
709                     child.set_value(path, value)
710                 else:
711                     setattr(child, path, value)
712             except PropertiesOptionError:
713                 pass
714
715     def find_firsts(self, byname=None, bypath=undefined, byoption=undefined,
716                     byvalue=undefined, display_error=True, _sub=False,
717                     check_properties=True):
718         """Find first not in current GroupConfig, but in each children
719         """
720         ret = []
721
722         #if MetaConfig, all children have same OptionDescription in context
723         #so search only one time the option for all children
724         if bypath is undefined and byname is not None and \
725                 isinstance(self, MetaConfig):
726             bypath = self._find(bytype=None, byvalue=undefined, byname=byname,
727                                 first=True, type_='path',
728                                 check_properties=None,
729                                 display_error=display_error)
730             byname = None
731             byoption = self.cfgimpl_get_description(
732             ).impl_get_opt_by_path(bypath)
733
734         for child in self._impl_children:
735             try:
736                 if isinstance(child, GroupConfig):
737                     ret.extend(child.find_firsts(byname=byname, bypath=bypath,
738                                                  byoption=byoption,
739                                                  byvalue=byvalue,
740                                                  check_properties=check_properties,
741                                                  display_error=False,
742                                                  _sub=True))
743                 else:
744                     child._find(None, byname, byvalue, first=True,
745                                 type_='path', display_error=False,
746                                 check_properties=check_properties,
747                                 only_path=bypath, only_option=byoption)
748                     ret.append(child)
749             except AttributeError:
750                 pass
751         if _sub:
752             return ret
753         else:
754             return GroupConfig(self._find_return_results(ret, display_error))
755
756     def __repr__(self):
757         return object.__repr__(self)
758
759     def __str__(self):
760         ret = ''
761         for child in self._impl_children:
762             ret += '({0})\n'.format(child._impl_name)
763         try:
764             ret += super(GroupConfig, self).__str__()
765         except ConfigError:
766             pass
767         return ret
768
769     def getattr(self, name, force_permissive=False, validate=True):
770         for child in self._impl_children:
771             if name == child._impl_name:
772                 return child
773         return super(GroupConfig, self).getattr(name, force_permissive,
774                                                 validate)
775
776
777 class MetaConfig(GroupConfig):
778     __slots__ = tuple()
779
780     def __init__(self, children, session_id=None, persistent=False,
781                  name=undefined):
782         descr = None
783         for child in children:
784             if not isinstance(child, _CommonConfig):
785                 raise TypeError(_("metaconfig's children "
786                                   "should be config, not {0}"
787                                   ).format(type(child)))
788             if child.cfgimpl_get_meta() is not None:
789                 raise ValueError(_("child has already a metaconfig's"))
790             if descr is None:
791                 descr = child.cfgimpl_get_description()
792             elif not descr is child.cfgimpl_get_description():
793                 raise ValueError(_('all config in metaconfig must '
794                                    'have the same optiondescription'))
795             child._impl_meta = weakref.ref(self)
796
797         super(MetaConfig, self).__init__(children, session_id, persistent,
798                                          descr, name)
799
800     def set_value(self, path, value, force_default=False,
801                   force_dont_change_value=False, force_default_if_same=False,
802                   only_config=False):
803         if only_config:
804             if force_default or force_default_if_same or force_dont_change_value:
805                 raise ValueError(_('force_default, force_default_if_same or '
806                                    'force_dont_change_value cannot be set with'
807                                    ' only_config'))
808             return super(MetaConfig, self).set_value(path, value)
809         if force_default or force_default_if_same or force_dont_change_value:
810             if force_default and force_dont_change_value:
811                 raise ValueError(_('force_default and force_dont_change_value'
812                                    ' cannot be set together'))
813             opt = self.cfgimpl_get_description().impl_get_opt_by_path(path)
814             for child in self._impl_children:
815                 if force_default_if_same or force_default:
816                     if force_default_if_same:
817                         if not child.cfgimpl_get_values()._contains(path):
818                             child_value = undefined
819                         else:
820                             child_value = child.getattr(path)
821                     if force_default or value == child_value:
822                         child.cfgimpl_get_values().reset(opt, path=path,
823                                                          validate=False)
824                         continue
825                 if force_dont_change_value:
826                     child_value = child.getattr(path)
827                     if value != child_value:
828                         setattr(child, path, child_value)
829
830         setattr(self, path, value)