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