optimise mandatory_warnings
[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                 _setting_properties=undefined):
246         """
247         attribute notation mechanism for accessing the value of an option
248         :param name: attribute name
249         :return: option's value if name is an option name, OptionDescription
250                  otherwise
251         """
252         # attribute access by passing a path,
253         # for instance getattr(self, "creole.general.family.adresse_ip_eth0")
254         if '.' in name:
255             homeconfig, name = self.cfgimpl_get_home_by_path(
256                 name, force_permissive=force_permissive)
257             return homeconfig.getattr(name, force_permissive=force_permissive,
258                                       validate=validate,
259                                       _setting_properties=_setting_properties)
260         context = self._cfgimpl_get_context()
261         option = self.cfgimpl_get_description().__getattr__(name,
262                                                             context=context)
263         subpath = self._get_subpath(name)
264         if isinstance(option, DynSymLinkOption):
265             return self.cfgimpl_get_values()._get_cached_item(
266                 option, path=subpath,
267                 validate=validate,
268                 force_permissive=force_permissive,
269                 setting_properties=_setting_properties)
270         elif isinstance(option, SymLinkOption):  # pragma: no dynoptiondescription cover
271             path = context.cfgimpl_get_description().impl_get_path_by_opt(
272                 option._impl_getopt())
273             return context.getattr(path, validate=validate,
274                                    force_permissive=force_permissive,
275                                    _setting_properties=_setting_properties)
276         elif option.impl_is_optiondescription():
277             self.cfgimpl_get_settings().validate_properties(
278                 option, True, False, path=subpath,
279                 force_permissive=force_permissive,
280                 setting_properties=_setting_properties)
281             return SubConfig(option, self._impl_context, subpath)
282         else:
283             return self.cfgimpl_get_values()._get_cached_item(
284                 option, path=subpath,
285                 validate=validate,
286                 force_permissive=force_permissive,
287                 setting_properties=_setting_properties)
288
289     def find(self, bytype=None, byname=None, byvalue=undefined, type_='option',
290              check_properties=True, force_permissive=False):
291         """
292             finds a list of options recursively in the config
293
294             :param bytype: Option class (BoolOption, StrOption, ...)
295             :param byname: filter by Option.impl_getname()
296             :param byvalue: filter by the option's value
297             :returns: list of matching Option objects
298         """
299         return self._cfgimpl_get_context()._find(bytype, byname, byvalue,
300                                                  first=False,
301                                                  type_=type_,
302                                                  _subpath=self.cfgimpl_get_path(False),
303                                                  check_properties=check_properties,
304                                                  force_permissive=force_permissive)
305
306     def find_first(self, bytype=None, byname=None, byvalue=undefined,
307                    type_='option', display_error=True, check_properties=True,
308                    force_permissive=False):
309         """
310             finds an option recursively in the config
311
312             :param bytype: Option class (BoolOption, StrOption, ...)
313             :param byname: filter by Option.impl_getname()
314             :param byvalue: filter by the option's value
315             :returns: list of matching Option objects
316         """
317         return self._cfgimpl_get_context()._find(
318             bytype, byname, byvalue, first=True, type_=type_,
319             _subpath=self.cfgimpl_get_path(False), display_error=display_error,
320             check_properties=check_properties,
321             force_permissive=force_permissive)
322
323     def _find(self, bytype, byname, byvalue, first, type_='option',
324               _subpath=None, check_properties=True, display_error=True,
325               force_permissive=False, only_path=undefined,
326               only_option=undefined):
327         """
328         convenience method for finding an option that lives only in the subtree
329
330         :param first: return only one option if True, a list otherwise
331         :return: find list or an exception if nothing has been found
332         """
333
334         def _filter_by_value():
335             if byvalue is undefined:
336                 return True
337             try:
338                 value = self.getattr(path, force_permissive=force_permissive)
339                 if isinstance(value, Multi):
340                     return byvalue in value
341                 else:
342                     return value == byvalue
343             # a property is a restriction upon the access of the value
344             except PropertiesOptionError:  # pragma: optional cover
345                 return False
346
347         if type_ not in ('option', 'path', 'value'):  # pragma: optional cover
348             raise ValueError(_('unknown type_ type {0}'
349                                'for _find').format(type_))
350         find_results = []
351         # if value and/or check_properties are set, need all avalaible option
352         # If first one has no good value or not good property check second one
353         # and so on
354         only_first = first is True and byvalue is None and \
355             check_properties is None
356         if only_path is not undefined:
357             options = [(only_path, only_option)]
358         else:
359             options = self.cfgimpl_get_description().impl_get_options_paths(
360                 bytype, byname, _subpath, only_first,
361                 self._cfgimpl_get_context())
362         for path, option in options:
363             if not _filter_by_value():
364                 continue
365             #remove option with propertyerror, ...
366             if byvalue is undefined and check_properties:
367                 try:
368                     value = self.getattr(path,
369                                          force_permissive=force_permissive)
370                 except PropertiesOptionError:  # pragma: optional cover
371                     # a property restricts the access of the value
372                     continue
373             if type_ == 'value':
374                 retval = value
375             elif type_ == 'path':
376                 retval = path
377             elif type_ == 'option':
378                 retval = option
379             if first:
380                 return retval
381             else:
382                 find_results.append(retval)
383         return self._find_return_results(find_results, display_error)
384
385     def _find_return_results(self, find_results, display_error):
386         if find_results == []:  # pragma: optional cover
387             if display_error:
388                 raise AttributeError(_("no option found in config"
389                                        " with these criteria"))
390             else:
391                 # translation is slow
392                 raise AttributeError()
393         else:
394             return find_results
395
396     def make_dict(self, flatten=False, _currpath=None, withoption=None,
397                   withvalue=undefined, force_permissive=False):
398         """exports the whole config into a `dict`, for example:
399
400         >>> print cfg.make_dict()
401         {'od2.var4': None, 'od2.var5': None, 'od2.var6': None}
402
403
404
405         :param flatten: returns a dict(name=value) instead of a dict(path=value)
406                         ::
407
408                             >>> print cfg.make_dict(flatten=True)
409                             {'var5': None, 'var4': None, 'var6': None}
410
411         :param withoption: returns the options that are present in the very same
412                            `OptionDescription` than the `withoption` itself::
413
414                                 >>> print cfg.make_dict(withoption='var1')
415                                 {'od2.var4': None, 'od2.var5': None,
416                                 'od2.var6': None,
417                                 'od2.var1': u'value',
418                                 'od1.var1': None,
419                                 'od1.var3': None,
420                                 'od1.var2': None}
421
422         :param withvalue: returns the options that have the value `withvalue`
423                           ::
424
425                             >>> print c.make_dict(withoption='var1',
426                                                   withvalue=u'value')
427                             {'od2.var4': None,
428                             'od2.var5': None,
429                             'od2.var6': None,
430                             'od2.var1': u'value'}
431
432         :returns: dict of Option's name (or path) and values
433         """
434         pathsvalues = []
435         if _currpath is None:
436             _currpath = []
437         if withoption is None and withvalue is not undefined:  # pragma: optional cover
438             raise ValueError(_("make_dict can't filtering with value without "
439                                "option"))
440         if withoption is not None:
441             context = self._cfgimpl_get_context()
442             for path in context._find(bytype=None, byname=withoption,
443                                       byvalue=withvalue, first=False,
444                                       type_='path', _subpath=self.cfgimpl_get_path(False),
445                                       force_permissive=force_permissive):
446                 path = '.'.join(path.split('.')[:-1])
447                 opt = context.unwrap_from_path(path, force_permissive=True)
448                 mypath = self.cfgimpl_get_path()
449                 if mypath is not None:
450                     if mypath == path:
451                         withoption = None
452                         withvalue = undefined
453                         break
454                     else:
455                         tmypath = mypath + '.'
456                         if not path.startswith(tmypath):  # pragma: optional cover
457                             raise AttributeError(_('unexpected path {0}, '
458                                                    'should start with {1}'
459                                                    '').format(path, mypath))
460                         path = path[len(tmypath):]
461                 self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten,
462                                     force_permissive=force_permissive)
463         #withoption can be set to None below !
464         if withoption is None:
465             for opt in self.cfgimpl_get_description().impl_getchildren():
466                 path = opt.impl_getname()
467                 self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten,
468                                     force_permissive=force_permissive)
469         if _currpath == []:
470             options = dict(pathsvalues)
471             return options
472         return pathsvalues
473
474     def _make_sub_dict(self, opt, path, pathsvalues, _currpath, flatten,
475                        force_permissive=False):
476         try:
477             if opt.impl_is_optiondescription():
478                 pathsvalues += self.getattr(path,
479                                             force_permissive=force_permissive).make_dict(
480                                                 flatten,
481                                                 _currpath + path.split('.'),
482                                                 force_permissive=force_permissive)
483             else:
484                 value = self.getattr(opt.impl_getname(),
485                                      force_permissive=force_permissive)
486                 if flatten:
487                     name = opt.impl_getname()
488                 else:
489                     name = '.'.join(_currpath + [opt.impl_getname()])
490                 pathsvalues.append((name, value))
491         except PropertiesOptionError:  # pragma: optional cover
492             pass
493
494     def cfgimpl_get_path(self, dyn=True):
495         descr = self.cfgimpl_get_description()
496         if not dyn and descr.impl_is_dynoptiondescription():
497             context_descr = self._cfgimpl_get_context().cfgimpl_get_description()
498             return context_descr.impl_get_path_by_opt(descr._impl_getopt())
499         return self._impl_path
500
501
502 class _CommonConfig(SubConfig):
503     "abstract base class for the Config, GroupConfig and the MetaConfig"
504     __slots__ = ('_impl_values', '_impl_settings', '_impl_meta', '_impl_test')
505
506     def _impl_build_all_caches(self):
507         descr = self.cfgimpl_get_description()
508         if not descr.impl_already_build_caches():
509             descr.impl_build_cache_consistency()
510             descr.impl_build_cache_option()
511             descr.impl_validate_options()
512
513     def read_only(self):
514         "read only is a global config's setting, see `settings.py`"
515         self.cfgimpl_get_settings().read_only()
516
517     def read_write(self):
518         "read write is a global config's setting, see `settings.py`"
519         self.cfgimpl_get_settings().read_write()
520
521     def getowner(self, opt, force_permissive=False):
522         """convenience method to retrieve an option's owner
523         from the config itself
524         """
525         if not isinstance(opt, Option) and \
526                 not isinstance(opt, SymLinkOption) and \
527                 not isinstance(opt, DynSymLinkOption):  # pragma: optional cover
528             raise TypeError(_('opt in getowner must be an option not {0}'
529                               '').format(type(opt)))
530         return self.cfgimpl_get_values().getowner(opt,
531                                                   force_permissive=force_permissive)
532
533     def unwrap_from_path(self, path, force_permissive=False):
534         """convenience method to extract and Option() object from the Config()
535         and it is **fast**: finds the option directly in the appropriate
536         namespace
537
538         :returns: Option()
539         """
540         context = self._cfgimpl_get_context()
541         if '.' in path:
542             homeconfig, path = self.cfgimpl_get_home_by_path(
543                 path, force_permissive=force_permissive)
544             return homeconfig.cfgimpl_get_description().__getattr__(path, context=context)
545         return self.cfgimpl_get_description().__getattr__(path, context=context)
546
547     def cfgimpl_get_path(self, dyn=True):
548         return None
549
550     def cfgimpl_get_meta(self):
551         if self._impl_meta is not None:
552             return self._impl_meta()
553
554     # information
555     def impl_set_information(self, key, value):
556         """updates the information's attribute
557
558         :param key: information's key (ex: "help", "doc"
559         :param value: information's value (ex: "the help string")
560         """
561         self._impl_values.set_information(key, value)
562
563     def impl_get_information(self, key, default=undefined):
564         """retrieves one information's item
565
566         :param key: the item string (ex: "help")
567         """
568         return self._impl_values.get_information(key, default)
569
570     # ----- state
571     def __getstate__(self):
572         if self._impl_meta is not None:
573             raise ConfigError(_('cannot serialize Config with MetaConfig'))  # pragma: optional cover
574         slots = set()
575         for subclass in self.__class__.__mro__:
576             if subclass is not object:
577                 slots.update(subclass.__slots__)
578         slots -= frozenset(['_impl_context', '_impl_meta', '__weakref__'])
579         state = {}
580         for slot in slots:
581             try:
582                 state[slot] = getattr(self, slot)
583             except AttributeError:  # pragma: optional cover
584                 pass
585         storage = self._impl_values._p_._storage
586         if not storage.serializable:
587             raise ConfigError(_('this storage is not serialisable, could be a '
588                               'none persistent storage'))  # pragma: optional cover
589         state['_storage'] = {'session_id': storage.session_id,
590                              'persistent': storage.persistent}
591         state['_impl_setting'] = _impl_getstate_setting()
592         return state
593
594     def __setstate__(self, state):
595         for key, value in state.items():
596             if key not in ['_storage', '_impl_setting']:
597                 setattr(self, key, value)
598         set_storage('config', **state['_impl_setting'])
599         self._impl_context = weakref.ref(self)
600         self._impl_settings.context = weakref.ref(self)
601         self._impl_values.context = weakref.ref(self)
602         storage = get_storage('config', test=self._impl_test, **state['_storage'])
603         self._impl_values._impl_setstate(storage)
604         self._impl_settings._impl_setstate(storage)
605         self._impl_meta = None
606
607     def _gen_fake_values(self):
608         fake_config = Config(self._impl_descr, persistent=False,
609                              force_values=get_storages_validation(),
610                              force_settings=self.cfgimpl_get_settings())
611         fake_config.cfgimpl_get_values()._p_._values = self.cfgimpl_get_values()._p_.get_modified_values()
612         return fake_config
613
614     def duplicate(self):
615         config = Config(self._impl_descr)
616         config.cfgimpl_get_values()._p_._values = self.cfgimpl_get_values()._p_.get_modified_values()
617         config.cfgimpl_get_settings()._p_._properties = self.cfgimpl_get_settings()._p_.get_modified_properties()
618         config.cfgimpl_get_settings()._p_._permissives = self.cfgimpl_get_settings()._p_.get_modified_permissives()
619         return config
620
621
622 # ____________________________________________________________
623 class Config(_CommonConfig):
624     "main configuration management entry"
625     __slots__ = ('__weakref__', '_impl_test', '_impl_name')
626
627     def __init__(self, descr, session_id=None, persistent=False,
628                  name=undefined, force_values=None, force_settings=None):
629         """ Configuration option management master class
630
631         :param descr: describes the configuration schema
632         :type descr: an instance of ``option.OptionDescription``
633         :param context: the current root config
634         :type context: `Config`
635         :param session_id: session ID is import with persistent Config to
636         retrieve good session
637         :type session_id: `str`
638         :param persistent: if persistent, don't delete storage when leaving
639         :type persistent: `boolean`
640         """
641         if force_settings is not None and force_values is not None:
642             self._impl_settings = force_settings
643             self._impl_values = Values(self, force_values)
644         else:
645             settings, values = get_storages(self, session_id, persistent)
646             if name is undefined:
647                 name = 'config'
648                 if session_id is not None:
649                     name += session_id
650             if name is not None and not valid_name(name):  # pragma: optional cover
651                 raise ValueError(_("invalid name: {0} for config").format(name))
652             self._impl_settings = Settings(self, settings)
653             self._impl_values = Values(self, values)
654         super(Config, self).__init__(descr, weakref.ref(self))
655         self._impl_build_all_caches()
656         self._impl_meta = None
657         #undocumented option used only in test script
658         self._impl_test = False
659         self._impl_name = name
660
661     def cfgimpl_reset_cache(self,
662                             only_expired=False,
663                             only=('values', 'settings')):
664         if 'values' in only:
665             self.cfgimpl_get_values().reset_cache(only_expired=only_expired)
666         if 'settings' in only:
667             self.cfgimpl_get_settings().reset_cache(only_expired=only_expired)
668
669     def impl_getname(self):
670         return self._impl_name
671
672     def impl_setname(self, name):
673         self._impl_name = name
674
675
676 class GroupConfig(_CommonConfig):
677     __slots__ = ('__weakref__', '_impl_children', '_impl_name')
678
679     def __init__(self, children, session_id=None, persistent=False,
680                  _descr=None, name=undefined):
681         if not isinstance(children, list):
682             raise ValueError(_("groupconfig's children must be a list"))
683         names = []
684         for child in children:
685             if not isinstance(child, _CommonConfig):
686                 raise ValueError(_("groupconfig's children must be Config, MetaConfig or GroupConfig"))
687             name = child._impl_name
688             if name is None:
689                 raise ValueError(_('name must be set to config before creating groupconfig'))
690             #if name in names:
691             #    raise ValueError(_('config name must be uniq in groupconfig'))
692             names.append(name)
693         if len(names) != len(set(names)):
694             for idx in xrange(1, len(names) + 1):
695                 name = names.pop(0)
696                 if name in names:
697                     raise ConflictError(_('config name must be uniq in '
698                                           'groupconfig for {0}').format(name))
699         self._impl_children = children
700         settings, values = get_storages(self, session_id, persistent)
701         self._impl_settings = Settings(self, settings)
702         self._impl_values = Values(self, values)
703         super(GroupConfig, self).__init__(_descr, weakref.ref(self))
704         self._impl_meta = None
705         #undocumented option used only in test script
706         self._impl_test = False
707         if name is undefined:
708             name = session_id
709         self._impl_name = name
710
711     def cfgimpl_get_children(self):
712         return self._impl_children
713
714     #def cfgimpl_get_context(self):
715     #    "a meta config is a config which has a setting, that is itself"
716     #    return self
717
718     def cfgimpl_reset_cache(self,
719                             only_expired=False,
720                             only=('values', 'settings')):
721         if 'values' in only:
722             self.cfgimpl_get_values().reset_cache(only_expired=only_expired)
723         if 'settings' in only:
724             self.cfgimpl_get_settings().reset_cache(only_expired=only_expired)
725         for child in self._impl_children:
726             child.cfgimpl_reset_cache(only_expired=only_expired, only=only)
727
728     def set_value(self, path, value):
729         """Setattr not in current GroupConfig, but in each children
730         """
731         for child in self._impl_children:
732             try:
733                 if isinstance(child, MetaConfig):
734                     child.set_value(path, value, only_config=True)
735                 elif isinstance(child, GroupConfig):
736                     child.set_value(path, value)
737                 else:
738                     setattr(child, path, value)
739             except PropertiesOptionError:
740                 pass
741
742     def find_firsts(self, byname=None, bypath=undefined, byoption=undefined,
743                     byvalue=undefined, display_error=True, _sub=False,
744                     check_properties=True):
745         """Find first not in current GroupConfig, but in each children
746         """
747         ret = []
748
749         #if MetaConfig, all children have same OptionDescription in context
750         #so search only one time the option for all children
751         if bypath is undefined and byname is not None and \
752                 isinstance(self, MetaConfig):
753             bypath = self._find(bytype=None, byvalue=undefined, byname=byname,
754                                 first=True, type_='path',
755                                 check_properties=None,
756                                 display_error=display_error)
757             byname = None
758             byoption = self.cfgimpl_get_description(
759             ).impl_get_opt_by_path(bypath)
760
761         for child in self._impl_children:
762             try:
763                 if isinstance(child, GroupConfig):
764                     ret.extend(child.find_firsts(byname=byname, bypath=bypath,
765                                                  byoption=byoption,
766                                                  byvalue=byvalue,
767                                                  check_properties=check_properties,
768                                                  display_error=False,
769                                                  _sub=True))
770                 else:
771                     child._find(None, byname, byvalue, first=True,
772                                 type_='path', display_error=False,
773                                 check_properties=check_properties,
774                                 only_path=bypath, only_option=byoption)
775                     ret.append(child)
776             except AttributeError:
777                 pass
778         if _sub:
779             return ret
780         else:
781             return GroupConfig(self._find_return_results(ret, display_error))
782
783     def __repr__(self):
784         return object.__repr__(self)
785
786     def __str__(self):
787         ret = ''
788         for child in self._impl_children:
789             ret += '({0})\n'.format(child._impl_name)
790         try:
791             ret += super(GroupConfig, self).__str__()
792         except ConfigError:
793             pass
794         return ret
795
796     def getattr(self, name, force_permissive=False, validate=True):
797         for child in self._impl_children:
798             if name == child._impl_name:
799                 return child
800         return super(GroupConfig, self).getattr(name, force_permissive,
801                                                 validate)
802
803
804 class MetaConfig(GroupConfig):
805     __slots__ = tuple()
806
807     def __init__(self, children, session_id=None, persistent=False,
808                  name=undefined):
809         descr = None
810         for child in children:
811             if not isinstance(child, _CommonConfig):
812                 raise TypeError(_("metaconfig's children "
813                                   "should be config, not {0}"
814                                   ).format(type(child)))
815             if child.cfgimpl_get_meta() is not None:
816                 raise ValueError(_("child has already a metaconfig's"))
817             if descr is None:
818                 descr = child.cfgimpl_get_description()
819             elif not descr is child.cfgimpl_get_description():
820                 raise ValueError(_('all config in metaconfig must '
821                                    'have the same optiondescription'))
822             child._impl_meta = weakref.ref(self)
823
824         super(MetaConfig, self).__init__(children, session_id, persistent,
825                                          descr, name)
826
827     def set_value(self, path, value, force_default=False,
828                   force_dont_change_value=False, force_default_if_same=False,
829                   only_config=False):
830         if only_config:
831             if force_default or force_default_if_same or force_dont_change_value:
832                 raise ValueError(_('force_default, force_default_if_same or '
833                                    'force_dont_change_value cannot be set with'
834                                    ' only_config'))
835             return super(MetaConfig, self).set_value(path, value)
836         if force_default or force_default_if_same or force_dont_change_value:
837             if force_default and force_dont_change_value:
838                 raise ValueError(_('force_default and force_dont_change_value'
839                                    ' cannot be set together'))
840             opt = self.cfgimpl_get_description().impl_get_opt_by_path(path)
841             for child in self._impl_children:
842                 if force_default_if_same or force_default:
843                     if force_default_if_same:
844                         if not child.cfgimpl_get_values()._contains(path):
845                             child_value = undefined
846                         else:
847                             child_value = child.getattr(path)
848                     if force_default or value == child_value:
849                         child.cfgimpl_get_values().reset(opt, path=path,
850                                                          validate=False)
851                         continue
852                 if force_dont_change_value:
853                     child_value = child.getattr(path)
854                     if value != child_value:
855                         setattr(child, path, child_value)
856
857         setattr(self, path, value)