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